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 #include "qgsrastertransparency.h"
22 #include "qgsrasterinterface.h"
23 #include "qgsrasterblock.h"
24 #include "qgsrectangle.h"
25 #include "qgsmessagelog.h"
26 #include <memory>
27
28 #ifdef HAVE_OPENCL
29 #ifdef QGISDEBUG
30 #include <chrono>
31 #include "qgssettings.h"
32 #endif
33 #include "qgsexception.h"
34 #include "qgsopenclutils.h"
35 #endif
36
QgsHillshadeRenderer(QgsRasterInterface * input,int band,double lightAzimuth,double lightAngle)37 QgsHillshadeRenderer::QgsHillshadeRenderer( QgsRasterInterface *input, int band, double lightAzimuth, double lightAngle ):
38 QgsRasterRenderer( input, QStringLiteral( "hillshade" ) )
39 , mBand( band )
40 , mZFactor( 1 )
41 , mLightAngle( lightAngle )
42 , mLightAzimuth( lightAzimuth )
43 , mMultiDirectional( false )
44 {
45
46 }
47
clone() const48 QgsHillshadeRenderer *QgsHillshadeRenderer::clone() const
49 {
50 QgsHillshadeRenderer *r = new QgsHillshadeRenderer( nullptr, mBand, mLightAzimuth, mLightAngle );
51 r->copyCommonProperties( this );
52
53 r->setZFactor( mZFactor );
54 r->setMultiDirectional( mMultiDirectional );
55 return r;
56 }
57
create(const QDomElement & elem,QgsRasterInterface * input)58 QgsRasterRenderer *QgsHillshadeRenderer::create( const QDomElement &elem, QgsRasterInterface *input )
59 {
60 if ( elem.isNull() )
61 {
62 return nullptr;
63 }
64
65 int band = elem.attribute( QStringLiteral( "band" ), QStringLiteral( "0" ) ).toInt();
66 double azimuth = elem.attribute( QStringLiteral( "azimuth" ), QStringLiteral( "315" ) ).toDouble();
67 double angle = elem.attribute( QStringLiteral( "angle" ), QStringLiteral( "45" ) ).toDouble();
68 double zFactor = elem.attribute( QStringLiteral( "zfactor" ), QStringLiteral( "1" ) ).toDouble();
69 bool multiDirectional = elem.attribute( QStringLiteral( "multidirection" ), QStringLiteral( "0" ) ).toInt();
70 QgsHillshadeRenderer *r = new QgsHillshadeRenderer( input, band, azimuth, angle );
71 r->readXml( elem );
72
73 r->setZFactor( zFactor );
74 r->setMultiDirectional( multiDirectional );
75 return r;
76 }
77
writeXml(QDomDocument & doc,QDomElement & parentElem) const78 void QgsHillshadeRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
79 {
80 if ( parentElem.isNull() )
81 {
82 return;
83 }
84
85 QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
86 _writeXml( doc, rasterRendererElem );
87
88 rasterRendererElem.setAttribute( QStringLiteral( "band" ), mBand );
89 rasterRendererElem.setAttribute( QStringLiteral( "azimuth" ), QString::number( mLightAzimuth ) );
90 rasterRendererElem.setAttribute( QStringLiteral( "angle" ), QString::number( mLightAngle ) );
91 rasterRendererElem.setAttribute( QStringLiteral( "zfactor" ), QString::number( mZFactor ) );
92 rasterRendererElem.setAttribute( QStringLiteral( "multidirection" ), QString::number( mMultiDirectional ) );
93 parentElem.appendChild( rasterRendererElem );
94 }
95
block(int bandNo,const QgsRectangle & extent,int width,int height,QgsRasterBlockFeedback * feedback)96 QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
97 {
98 Q_UNUSED( bandNo )
99 std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
100 if ( !mInput )
101 {
102 QgsDebugMsg( QStringLiteral( "No input raster!" ) );
103 return outputBlock.release();
104 }
105
106 std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mBand, extent, width, height, feedback ) );
107
108 if ( !inputBlock || inputBlock->isEmpty() )
109 {
110 QgsDebugMsg( QStringLiteral( "No raster data!" ) );
111 return outputBlock.release();
112 }
113
114 std::shared_ptr< QgsRasterBlock > alphaBlock;
115
116 if ( mAlphaBand > 0 && mBand != mAlphaBand )
117 {
118 alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
119 if ( !alphaBlock || alphaBlock->isEmpty() )
120 {
121 // TODO: better to render without alpha
122 return outputBlock.release();
123 }
124 }
125 else if ( mAlphaBand > 0 )
126 {
127 alphaBlock = inputBlock;
128 }
129
130 if ( !outputBlock->reset( Qgis::DataType::ARGB32_Premultiplied, width, height ) )
131 {
132 return outputBlock.release();
133 }
134
135 // Starting the computation
136
137 // Common pre-calculated values
138 float cellXSize = static_cast<float>( extent.width() ) / width;
139 float cellYSize = static_cast<float>( extent.height() ) / height;
140 float zenithRad = static_cast<float>( std::max( 0.0, 90 - mLightAngle ) * M_PI / 180.0 );
141 float azimuthRad = static_cast<float>( -1 * mLightAzimuth * M_PI / 180.0 );
142 float cosZenithRad = std::cos( zenithRad );
143 float sinZenithRad = std::sin( zenithRad );
144
145 // For fast formula from GDAL DEM
146 float cos_alt_mul_z = cosZenithRad * static_cast<float>( mZFactor );
147 float cos_az_mul_cos_alt_mul_z = std::cos( azimuthRad ) * cos_alt_mul_z;
148 float sin_az_mul_cos_alt_mul_z = std::sin( azimuthRad ) * cos_alt_mul_z;
149 float cos_az_mul_cos_alt_mul_z_mul_254 = 254.0f * cos_az_mul_cos_alt_mul_z;
150 float sin_az_mul_cos_alt_mul_z_mul_254 = 254.0f * sin_az_mul_cos_alt_mul_z;
151 float square_z = static_cast<float>( mZFactor * mZFactor );
152 float sin_altRadians_mul_254 = 254.0f * sinZenithRad;
153
154 // For multi directional
155 float sin_altRadians_mul_127 = 127.0f * sinZenithRad;
156 // 127.0 * std::cos(225.0 * M_PI / 180.0) = -32.87001872802012
157 float cos225_az_mul_cos_alt_mul_z_mul_127 = -32.87001872802012f * cos_alt_mul_z;
158 float cos_alt_mul_z_mul_127 = 127.0f * cos_alt_mul_z;
159
160 const QRgb defaultNodataColor = renderColorForNodataPixel();
161
162 #ifdef HAVE_OPENCL
163
164 // Use OpenCL? For now OpenCL is enabled in the default configuration only
165 bool useOpenCL( QgsOpenClUtils::enabled()
166 && QgsOpenClUtils::available()
167 && ( ! mRasterTransparency || mRasterTransparency->isEmpty() )
168 && mAlphaBand <= 0
169 && inputBlock->dataTypeSize() <= 4 );
170 // Check for sources
171 QString source;
172 if ( useOpenCL )
173 {
174 source = QgsOpenClUtils::sourceFromBaseName( QStringLiteral( "hillshade_renderer" ) );
175 if ( source.isEmpty() )
176 {
177 useOpenCL = false;
178 QgsMessageLog::logMessage( QObject::tr( "Error loading OpenCL program source from path %1" ).arg( QgsOpenClUtils::sourcePath() ), QgsOpenClUtils::LOGMESSAGE_TAG, Qgis::MessageLevel::Critical );
179 }
180 }
181
182 #ifdef QGISDEBUG
183 std::chrono::time_point<std::chrono::system_clock> startTime( std::chrono::system_clock::now() );
184 #endif
185
186 if ( useOpenCL )
187 {
188
189 try
190 {
191 std::size_t inputDataTypeSize = inputBlock->dataTypeSize();
192 std::size_t outputDataTypeSize = outputBlock->dataTypeSize();
193 // Buffer scanline, 1px height, 2px wider
194 QString typeName;
195 switch ( inputBlock->dataType() )
196 {
197 case Qgis::DataType::Byte:
198 typeName = QStringLiteral( "unsigned char" );
199 break;
200 case Qgis::DataType::UInt16:
201 typeName = QStringLiteral( "unsigned int" );
202 break;
203 case Qgis::DataType::Int16:
204 typeName = QStringLiteral( "short" );
205 break;
206 case Qgis::DataType::UInt32:
207 typeName = QStringLiteral( "unsigned int" );
208 break;
209 case Qgis::DataType::Int32:
210 typeName = QStringLiteral( "int" );
211 break;
212 case Qgis::DataType::Float32:
213 typeName = QStringLiteral( "float" );
214 break;
215 default:
216 throw QgsException( QStringLiteral( "Unsupported data type for OpenCL processing." ) );
217 }
218
219 if ( inputBlock->dataType() != Qgis::DataType::Float32 )
220 {
221 source.replace( QLatin1String( "__global float *scanLine" ), QStringLiteral( "__global %1 *scanLine" ).arg( typeName ) );
222 }
223
224 // Data type for input is Float32 (4 bytes)
225 std::size_t scanLineWidth( inputBlock->width() + 2 );
226 std::size_t inputSize( inputDataTypeSize * inputBlock->width() );
227
228 // CL buffers are also 2px wider for nodata initial and final columns
229 std::size_t bufferWidth( width + 2 );
230 std::size_t bufferSize( inputDataTypeSize * bufferWidth );
231
232 // Buffer scanlines, 1px height, 2px wider
233 // Data type for input is Float32 (4 bytes)
234 // keep only three scanlines in memory at a time, make room for initial and final nodata
235 std::unique_ptr<QgsRasterBlock> scanLine = std::make_unique<QgsRasterBlock>( inputBlock->dataType(), scanLineWidth, 1 );
236 // Note: output block is not 2px wider and it is an image
237 // Prepare context and queue
238 cl::Context ctx = QgsOpenClUtils::context();
239 cl::CommandQueue queue = QgsOpenClUtils::commandQueue();
240
241 // Cast to float (because double just crashes on some GPUs)
242 std::vector<float> rasterParams;
243 rasterParams.push_back( inputBlock->noDataValue() );
244 rasterParams.push_back( outputBlock->noDataValue() );
245 rasterParams.push_back( mZFactor );
246 rasterParams.push_back( cellXSize );
247 rasterParams.push_back( cellYSize );
248 rasterParams.push_back( static_cast<float>( mOpacity ) ); // 5
249
250 // For fast formula from GDAL DEM
251 rasterParams.push_back( cos_az_mul_cos_alt_mul_z_mul_254 ); // 6
252 rasterParams.push_back( sin_az_mul_cos_alt_mul_z_mul_254 ); // 7
253 rasterParams.push_back( square_z ); // 8
254 rasterParams.push_back( sin_altRadians_mul_254 ); // 9
255
256 // For multidirectional fast formula
257 rasterParams.push_back( sin_altRadians_mul_127 ); // 10
258 rasterParams.push_back( cos225_az_mul_cos_alt_mul_z_mul_127 ); // 11
259 rasterParams.push_back( cos_alt_mul_z_mul_127 ); // 12
260
261 // Default color for nodata (BGR components)
262 rasterParams.push_back( static_cast<float>( qBlue( defaultNodataColor ) ) ); // 13
263 rasterParams.push_back( static_cast<float>( qGreen( defaultNodataColor ) ) ); // 14
264 rasterParams.push_back( static_cast<float>( qRed( defaultNodataColor ) ) ); // 15
265 rasterParams.push_back( static_cast<float>( qAlpha( defaultNodataColor ) ) / 255.0f ); // 16
266
267 // Whether use multidirectional
268 rasterParams.push_back( static_cast<float>( mMultiDirectional ) ); // 17
269
270 cl::Buffer rasterParamsBuffer( queue, rasterParams.begin(), rasterParams.end(), true, false, nullptr );
271 cl::Buffer scanLine1Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
272 cl::Buffer scanLine2Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
273 cl::Buffer scanLine3Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
274 cl::Buffer *scanLineBuffer[3] = {&scanLine1Buffer, &scanLine2Buffer, &scanLine3Buffer};
275 // Note that result buffer is an image
276 cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, outputDataTypeSize * width, nullptr, nullptr );
277
278 static std::map<Qgis::DataType, cl::Program> programCache;
279 cl::Program program = programCache[inputBlock->dataType()];
280 if ( ! program.get() )
281 {
282 // Create a program from the kernel source
283 programCache[inputBlock->dataType()] = QgsOpenClUtils::buildProgram( source, QgsOpenClUtils::ExceptionBehavior::Throw );
284 program = programCache[inputBlock->dataType()];
285 }
286
287 // Disable program cache when developing and testing cl program
288 // program = QgsOpenClUtils::buildProgram( ctx, source, QgsOpenClUtils::ExceptionBehavior::Throw );
289
290 // Create the OpenCL kernel
291 auto kernel = cl::KernelFunctor <
292 cl::Buffer &,
293 cl::Buffer &,
294 cl::Buffer &,
295 cl::Buffer &,
296 cl::Buffer &
297 > ( program, "processNineCellWindow" );
298
299
300 // Rotating buffer index
301 std::vector<int> rowIndex = {0, 1, 2};
302
303 for ( int i = 0; i < height; i++ )
304 {
305 if ( feedback && feedback->isCanceled() )
306 {
307 break;
308 }
309
310 if ( feedback )
311 {
312 feedback->setProgress( 100.0 * static_cast< double >( i ) / height );
313 }
314
315 if ( i == 0 )
316 {
317 // Fill scanline 1 with (input) nodata for the values above the first row and feed scanline2 with the first row
318 scanLine->resetNoDataValue();
319 queue.enqueueWriteBuffer( scanLine1Buffer, CL_TRUE, 0, bufferSize, scanLine->bits( ) );
320 // Read first row
321 memcpy( scanLine->bits( 0, 1 ), inputBlock->bits( i, 0 ), inputSize );
322 queue.enqueueWriteBuffer( scanLine2Buffer, CL_TRUE, 0, bufferSize, scanLine->bits( ) ); // row 0
323 // Second row
324 memcpy( scanLine->bits( 0, 1 ), inputBlock->bits( i + 1, 0 ), inputSize );
325 queue.enqueueWriteBuffer( scanLine3Buffer, CL_TRUE, 0, bufferSize, scanLine->bits( ) ); //
326 }
327 else
328 {
329 // Normally fetch only scanLine3 and move forward one row
330 // Read scanline 3, fill the last row with nodata values if it's the last iteration
331 if ( i == inputBlock->height() - 1 )
332 {
333 scanLine->resetNoDataValue();
334 queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, 0, bufferSize, scanLine->bits( ) );
335 }
336 else // Overwrite from input, skip first and last
337 {
338 queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, inputDataTypeSize * 1 /* offset 1 */, inputSize, inputBlock->bits( i + 1, 0 ) );
339 }
340 }
341
342 kernel( cl::EnqueueArgs(
343 queue,
344 cl::NDRange( width )
345 ),
346 *scanLineBuffer[rowIndex[0]],
347 *scanLineBuffer[rowIndex[1]],
348 *scanLineBuffer[rowIndex[2]],
349 resultLineBuffer,
350 rasterParamsBuffer
351 );
352
353 queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, outputDataTypeSize * outputBlock->width( ), outputBlock->bits( i, 0 ) );
354 std::rotate( rowIndex.begin(), rowIndex.begin() + 1, rowIndex.end() );
355 }
356 }
357 catch ( cl::Error &e )
358 {
359 QgsMessageLog::logMessage( QObject::tr( "Error running OpenCL program: %1 - %2" ).arg( e.what( ) ).arg( QgsOpenClUtils::errorText( e.err( ) ) ),
360 QgsOpenClUtils::LOGMESSAGE_TAG, Qgis::MessageLevel::Critical );
361 QgsOpenClUtils::setEnabled( false );
362 QgsMessageLog::logMessage( QObject::tr( "OpenCL has been disabled, you can re-enable it in the options dialog." ),
363 QgsOpenClUtils::LOGMESSAGE_TAG, Qgis::MessageLevel::Critical );
364 }
365
366 } // End of OpenCL processing path
367 else // Use the CPU and the original algorithm
368 {
369
370 #endif
371
372 for ( qgssize i = 0; i < static_cast<qgssize>( height ); i++ )
373 {
374
375 for ( qgssize j = 0; j < static_cast<qgssize>( width ); j++ )
376 {
377
378 if ( inputBlock->isNoData( i, j ) )
379 {
380 outputBlock->setColor( static_cast<int>( i ), static_cast<int>( j ), defaultNodataColor );
381 continue;
382 }
383
384 qgssize iUp, iDown, jLeft, jRight;
385 if ( i == 0 )
386 {
387 iUp = i;
388 iDown = i + 1;
389 }
390 else if ( i < static_cast<qgssize>( height ) - 1 )
391 {
392 iUp = i - 1;
393 iDown = i + 1;
394 }
395 else
396 {
397 iUp = i - 1;
398 iDown = i;
399 }
400
401 if ( j == 0 )
402 {
403 jLeft = j;
404 jRight = j + 1;
405 }
406 else if ( j < static_cast<qgssize>( width ) - 1 )
407 {
408 jLeft = j - 1;
409 jRight = j + 1;
410 }
411 else
412 {
413 jLeft = j - 1;
414 jRight = j;
415 }
416
417 double x11;
418 double x21;
419 double x31;
420 double x12;
421 double x22; // Working cell
422 double x32;
423 double x13;
424 double x23;
425 double x33;
426
427 // This is center cell. It is not nodata. Use this in place of nodata neighbors
428 x22 = inputBlock->value( i, j );
429
430 x11 = inputBlock->isNoData( iUp, jLeft ) ? x22 : inputBlock->value( iUp, jLeft );
431 x21 = inputBlock->isNoData( i, jLeft ) ? x22 : inputBlock->value( i, jLeft );
432 x31 = inputBlock->isNoData( iDown, jLeft ) ? x22 : inputBlock->value( iDown, jLeft );
433
434 x12 = inputBlock->isNoData( iUp, j ) ? x22 : inputBlock->value( iUp, j );
435 // x22
436 x32 = inputBlock->isNoData( iDown, j ) ? x22 : inputBlock->value( iDown, j );
437
438 x13 = inputBlock->isNoData( iUp, jRight ) ? x22 : inputBlock->value( iUp, jRight );
439 x23 = inputBlock->isNoData( i, jRight ) ? x22 : inputBlock->value( i, jRight );
440 x33 = inputBlock->isNoData( iDown, jRight ) ? x22 : inputBlock->value( iDown, jRight );
441
442 double derX = calcFirstDerX( x11, x21, x31, x12, x22, x32, x13, x23, x33, cellXSize );
443 double derY = calcFirstDerY( x11, x21, x31, x12, x22, x32, x13, x23, x33, cellYSize );
444
445 // Fast formula
446
447 double grayValue;
448 if ( !mMultiDirectional )
449 {
450 // Standard single direction hillshade
451 grayValue = std::clamp( ( sin_altRadians_mul_254 -
452 ( derY * cos_az_mul_cos_alt_mul_z_mul_254 -
453 derX * sin_az_mul_cos_alt_mul_z_mul_254 ) ) /
454 std::sqrt( 1 + square_z * ( derX * derX + derY * derY ) ),
455 0.0, 255.0 );
456 }
457 else
458 {
459 // Weighted multi direction as in http://pubs.usgs.gov/of/1992/of92-422/of92-422.pdf
460 // Fast formula from GDAL DEM
461 const float xx = derX * derX;
462 const float yy = derY * derY;
463 const float xx_plus_yy = xx + yy;
464 // Flat?
465 if ( xx_plus_yy == 0.0 )
466 {
467 grayValue = std::clamp( static_cast<float>( 1.0 + sin_altRadians_mul_254 ), 0.0f, 255.0f );
468 }
469 else
470 {
471 // ... then the shade value from different azimuth
472 float val225_mul_127 = sin_altRadians_mul_127 +
473 ( derX - derY ) * cos225_az_mul_cos_alt_mul_z_mul_127;
474 val225_mul_127 = ( val225_mul_127 <= 0.0 ) ? 0.0 : val225_mul_127;
475 float val270_mul_127 = sin_altRadians_mul_127 -
476 derX * cos_alt_mul_z_mul_127;
477 val270_mul_127 = ( val270_mul_127 <= 0.0 ) ? 0.0 : val270_mul_127;
478 float val315_mul_127 = sin_altRadians_mul_127 +
479 ( derX + derY ) * cos225_az_mul_cos_alt_mul_z_mul_127;
480 val315_mul_127 = ( val315_mul_127 <= 0.0 ) ? 0.0 : val315_mul_127;
481 float val360_mul_127 = sin_altRadians_mul_127 -
482 derY * cos_alt_mul_z_mul_127;
483 val360_mul_127 = ( val360_mul_127 <= 0.0 ) ? 0.0 : val360_mul_127;
484
485 // ... then the weighted shading
486 const float weight_225 = 0.5 * xx_plus_yy - derX * derY;
487 const float weight_270 = xx;
488 const float weight_315 = xx_plus_yy - weight_225;
489 const float weight_360 = yy;
490 const float cang_mul_127 = (
491 ( weight_225 * val225_mul_127 +
492 weight_270 * val270_mul_127 +
493 weight_315 * val315_mul_127 +
494 weight_360 * val360_mul_127 ) / xx_plus_yy ) /
495 ( 1 + square_z * xx_plus_yy );
496
497 grayValue = std::clamp( 1.0f + cang_mul_127, 0.0f, 255.0f );
498 }
499 }
500
501 double currentAlpha = mOpacity;
502 if ( mRasterTransparency )
503 {
504 currentAlpha = mRasterTransparency->alphaValue( x22, mOpacity * 255 ) / 255.0;
505 }
506 if ( mAlphaBand > 0 )
507 {
508 currentAlpha *= alphaBlock->value( i ) / 255.0;
509 }
510
511 if ( qgsDoubleNear( currentAlpha, 1.0 ) )
512 {
513 outputBlock->setColor( i, j, qRgba( grayValue, grayValue, grayValue, 255 ) );
514 }
515 else
516 {
517 outputBlock->setColor( i, j, qRgba( currentAlpha * grayValue, currentAlpha * grayValue, currentAlpha * grayValue, currentAlpha * 255 ) );
518 }
519 }
520 }
521
522 #ifdef HAVE_OPENCL
523 } // End of switch in case OpenCL is not available or enabled
524
525 #ifdef QGISDEBUG
526 if ( QgsSettings().value( QStringLiteral( "Map/logCanvasRefreshEvent" ), false ).toBool() )
527 {
528 QgsMessageLog::logMessage( QStringLiteral( "%1 processing time for hillshade (%2 x %3 ): %4 ms" )
529 .arg( useOpenCL ? QStringLiteral( "OpenCL" ) : QStringLiteral( "CPU" ) )
530 .arg( width )
531 .arg( height )
532 .arg( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now() - startTime ).count() ),
533 tr( "Rendering" ) );
534 }
535 #endif
536 #endif
537
538 return outputBlock.release();
539 }
540
usesBands() const541 QList<int> QgsHillshadeRenderer::usesBands() const
542 {
543 QList<int> bandList;
544 if ( mBand != -1 )
545 {
546 bandList << mBand;
547 }
548 return bandList;
549
550 }
551
setBand(int bandNo)552 void QgsHillshadeRenderer::setBand( int bandNo )
553 {
554 if ( bandNo > mInput->bandCount() || bandNo <= 0 )
555 {
556 return;
557 }
558 mBand = bandNo;
559 }
560
calcFirstDerX(double x11,double x21,double x31,double x12,double x22,double x32,double x13,double x23,double x33,double cellsize)561 double QgsHillshadeRenderer::calcFirstDerX( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33, double cellsize )
562 {
563 Q_UNUSED( x12 )
564 Q_UNUSED( x22 )
565 Q_UNUSED( x32 )
566 return ( ( x13 + x23 + x23 + x33 ) - ( x11 + x21 + x21 + x31 ) ) / ( 8 * cellsize );
567 }
568
calcFirstDerY(double x11,double x21,double x31,double x12,double x22,double x32,double x13,double x23,double x33,double cellsize)569 double QgsHillshadeRenderer::calcFirstDerY( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33, double cellsize )
570 {
571 Q_UNUSED( x21 )
572 Q_UNUSED( x22 )
573 Q_UNUSED( x23 )
574 return ( ( x31 + x32 + x32 + x33 ) - ( x11 + x12 + x12 + x13 ) ) / ( 8 * -cellsize );
575 }
576
toSld(QDomDocument & doc,QDomElement & element,const QVariantMap & props) const577 void QgsHillshadeRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
578 {
579 // create base structure
580 QgsRasterRenderer::toSld( doc, element, props );
581
582 // look for RasterSymbolizer tag
583 QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
584 if ( elements.size() == 0 )
585 return;
586
587 // there SHOULD be only one
588 QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
589
590 // add Channel Selection tags (if band is not default 1)
591 // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
592 // after opacity or geometry or as first element after sld:RasterSymbolizer
593 if ( band() != 1 )
594 {
595 QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
596 elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
597 if ( elements.size() != 0 )
598 {
599 rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
600 }
601 else
602 {
603 elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
604 if ( elements.size() != 0 )
605 {
606 rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
607 }
608 else
609 {
610 rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
611 }
612 }
613
614 // for gray band
615 QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
616 channelSelectionElem.appendChild( channelElem );
617
618 // set band
619 QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
620 sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( band() ) ) );
621 channelElem.appendChild( sourceChannelNameElem );
622 }
623
624 // add ShadedRelief tag
625 QDomElement shadedReliefElem = doc.createElement( QStringLiteral( "sld:ShadedRelief" ) );
626 rasterSymbolizerElem.appendChild( shadedReliefElem );
627
628 // brightnessOnly tag
629 QDomElement brightnessOnlyElem = doc.createElement( QStringLiteral( "sld:BrightnessOnly" ) );
630 brightnessOnlyElem.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
631 shadedReliefElem.appendChild( brightnessOnlyElem );
632
633 // ReliefFactor tag
634 QDomElement reliefFactorElem = doc.createElement( QStringLiteral( "sld:ReliefFactor" ) );
635 reliefFactorElem.appendChild( doc.createTextNode( QString::number( zFactor() ) ) );
636 shadedReliefElem.appendChild( reliefFactorElem );
637
638 // altitude VendorOption tag
639 QDomElement altitudeVendorOptionElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
640 altitudeVendorOptionElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "altitude" ) );
641 altitudeVendorOptionElem.appendChild( doc.createTextNode( QString::number( altitude() ) ) );
642 shadedReliefElem.appendChild( altitudeVendorOptionElem );
643
644 // azimuth VendorOption tag
645 QDomElement azimutVendorOptionElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
646 azimutVendorOptionElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "azimuth" ) );
647 azimutVendorOptionElem.appendChild( doc.createTextNode( QString::number( azimuth() ) ) );
648 shadedReliefElem.appendChild( azimutVendorOptionElem );
649
650 // multidirectional VendorOption tag
651 QDomElement multidirectionalVendorOptionElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
652 multidirectionalVendorOptionElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "multidirectional" ) );
653 multidirectionalVendorOptionElem.appendChild( doc.createTextNode( QString::number( multiDirectional() ) ) );
654 shadedReliefElem.appendChild( multidirectionalVendorOptionElem );
655 }
656