QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgselevationmap.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgselevationmap.cpp
3 --------------------------------------
4 Date : August 2022
5 Copyright : (C) 2022 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgselevationmap.h"
17
18#include <algorithm>
19#include <cmath>
20#include <memory>
21
22#include "qgsrasterblock.h"
23
24#include <QPainter>
25#include <QString>
26
27using namespace Qt::StringLiterals;
28
29static const int ELEVATION_OFFSET = 7900;
30static const int ELEVATION_SCALE = 1000;
31
32
33QgsElevationMap::QgsElevationMap( const QSize &size, float devicePixelRatio )
34 : mElevationImage( size, QImage::Format_ARGB32 )
35{
36 mElevationImage.fill( 0 );
37 mElevationImage.setDevicePixelRatio( devicePixelRatio );
38}
39
41 : mElevationImage( image )
42{}
43
45 : mElevationImage( other.mElevationImage )
46{
47 mPainter.reset();
48}
49
51{
52 double zScaled = ( z + ELEVATION_OFFSET ) * ELEVATION_SCALE;
53 unsigned int zInt = static_cast<unsigned int>( std::clamp( zScaled, 0., 16777215. ) ); // make sure to fit into 3 bytes
54 return QRgb( zInt | ( static_cast< unsigned int >( 0xff ) << 24 ) );
55}
56
58{
59 unsigned int zScaled = colorRaw & 0xffffff;
60 return ( ( float ) zScaled ) / ELEVATION_SCALE - ELEVATION_OFFSET;
61}
62
63std::unique_ptr<QgsElevationMap> QgsElevationMap::fromRasterBlock( QgsRasterBlock *block )
64{
65 auto elevMap = std::make_unique<QgsElevationMap>( QSize( block->width(), block->height() ) );
66 QRgb *dataPtr = reinterpret_cast<QRgb *>( elevMap->mElevationImage.bits() );
67 for ( int row = 0; row < block->height(); ++row )
68 {
69 for ( int col = 0; col < block->width(); ++col )
70 {
71 bool isNoData;
72 double value = block->valueAndNoData( row, col, isNoData );
73 if ( !isNoData )
74 *dataPtr = encodeElevation( static_cast<float>( value ) );
75 ++dataPtr;
76 }
77 }
78 return elevMap;
79}
80
82{
83 if ( &other == this )
84 return *this;
85
86 mPainter.reset();
87 mElevationImage = other.mElevationImage;
88 return *this;
89}
90
91void QgsElevationMap::applyEyeDomeLighting( QImage &img, int distance, float strength, float rendererScale ) const
92{
93 const int imgWidth = img.width(), imgHeight = img.height();
94 QRgb *imgPtr = reinterpret_cast<QRgb *>( img.bits() );
95 const QRgb *elevPtr = reinterpret_cast<const QRgb *>( mElevationImage.constBits() );
96
97 const int neighbours[] = { -1, 0, 1, 0, 0, -1, 0, 1 };
98 for ( int i = 0; i < imgWidth; ++i )
99 {
100 for ( int j = 0; j < imgHeight; ++j )
101 {
102 qgssize index = j * static_cast<qgssize>( imgWidth ) + i;
103 float factor = 0.0f;
104 float centerDepth = decodeElevation( elevPtr[ index ] );
105 if ( isNoData( elevPtr[ index ] ) )
106 continue;
107 for ( qgssize k = 0; k < 4; ++k )
108 {
109 float borderFactor = 1.0f;
110 int iNeighbour = std::clamp( i + distance * neighbours[2 * k], 0, imgWidth - 1 );
111 int jNeighbour = std::clamp( j + distance * neighbours[2 * k + 1], 0, imgHeight - 1 );
112 qgssize neighbourIndex = jNeighbour * static_cast<qgssize>( imgWidth ) + iNeighbour;
113
114 // neighbour is no data, we try to reach one pixel closer that is not no data
115 // and calculate a factor to take account of the reduction of the distance
116 if ( isNoData( elevPtr[ neighbourIndex ] ) )
117 {
118 if ( neighbours[2 * k] != 0 )
119 {
120 int corrI = iNeighbour;
121 int inc = neighbours[2 * k];
122 while ( corrI != i && isNoData( elevPtr[ neighbourIndex ] ) )
123 {
124 corrI -= inc;
125 neighbourIndex = jNeighbour * static_cast<qgssize>( imgWidth ) + corrI;
126 }
127 if ( corrI == i )
128 continue;
129
130 borderFactor = static_cast<float>( distance ) / static_cast<float>( std::abs( i - corrI ) ) ;
131 }
132
133 if ( neighbours[2 * k + 1] != 0 )
134 {
135 int corrJ = jNeighbour;
136 int inc = neighbours[2 * k + 1];
137 while ( corrJ != j && isNoData( elevPtr[ neighbourIndex ] ) )
138 {
139 corrJ -= inc;
140 neighbourIndex = corrJ * static_cast<qgssize>( imgWidth ) + iNeighbour;
141 }
142 if ( corrJ == j )
143 continue;
144
145 borderFactor = static_cast<float>( distance ) / static_cast<float>( std::abs( j - corrJ ) ) ;
146 }
147 }
148
149 // neighbour is outside the extent (right or left), we take the pixel on border
150 // and calculate a factor to take account of the reduction of the distance
151 if ( neighbours[2 * k] != 0 && std::abs( iNeighbour - i ) < distance )
152 {
153 if ( i > iNeighbour )
154 borderFactor = static_cast<float>( distance ) / static_cast<float>( i ) ;
155 else if ( i < iNeighbour )
156 borderFactor = static_cast<float>( distance ) / static_cast<float>( ( imgWidth - i - 1 ) ) ;
157 }
158 if ( neighbours[2 * k + 1] != 0 && std::abs( jNeighbour - j ) < distance )
159 {
160 if ( j > jNeighbour )
161 borderFactor = static_cast<float>( distance ) / static_cast<float>( j ) ;
162 else if ( j < jNeighbour )
163 borderFactor = static_cast<float>( distance ) / static_cast<float>( imgHeight - j - 1 ) ;
164 }
165
166 float neighbourDepth = decodeElevation( elevPtr[ neighbourIndex ] );
167 factor += std::max<float>( 0, -( centerDepth - neighbourDepth ) * borderFactor );
168 }
169 float shade = expf( -factor * strength / rendererScale );
170
171 QRgb c = imgPtr[ index ];
172 imgPtr[ index ] = qRgba( static_cast<int>( static_cast<float>( qRed( c ) ) * shade ),
173 static_cast<int>( static_cast<float>( qGreen( c ) ) * shade ),
174 static_cast<int>( static_cast<float>( qBlue( c ) ) * shade )
175 , qAlpha( c ) );
176 }
177 }
178}
179
180void QgsElevationMap::applyHillshading( QImage &img, bool multiDirectional, double altitude, double azimuth, double zFactor, double cellSizeX, double cellSizeY ) const
181{
182 // algs from src/raster/qgshillshaderenderer.cpp
183 double altRad = altitude * M_PI / 180.0;
184 double cos_altRadian = std::cos( altRad );
185 double sin_altRadian = std::sin( altRad );
186 double cos_alt_mul_z = cos_altRadian * zFactor ;
187 double azimuthRad = -1 * azimuth * M_PI / 180.0;
188 double cos_az_mul_cos_alt_mul_z = std::cos( azimuthRad ) * cos_alt_mul_z;
189 double sin_az_mul_cos_alt_mul_z = std::sin( azimuthRad ) * cos_alt_mul_z;
190
191 // From qgshillshaderenderer.cpp, -32.87001872802012 comes from ( 127.0 * std::cos(225.0 * M_PI / 180.0))
192 // but this expression actually equals -89.8025612106916. To be consistent, we keep -32.87001872802012
193 // and divide it by 254 because, here, values are between 0.0 and 1.0, not between 0 and 255
194 double cos225_az_mul_cos_alt_mul_z_mul_0_5 = -32.87001872802012 * cos_alt_mul_z / 255;
195 double square_z = zFactor * zFactor;
196
197 QRgb *imgPtr = reinterpret_cast<QRgb *>( img.bits() );
198 const QRgb *elevPtr = reinterpret_cast<const QRgb *>( mElevationImage.constBits() );
199
200 const int imgWidth = img.width(), imgHeight = img.height();
201
202 auto colRowToIndex = [&]( int c, int r )->qgssize
203 {
204 return static_cast<qgssize>( r ) * imgWidth + c ;
205 };
206
207 float noData = noDataValue();
208
209 // Elevation value matrix
210 // 0 1 2 11 12 13
211 // 3 4 5 21 22 23
212 // 6 7 8 31 32 33
213 float pixelValues[9];
214
215 for ( int rowC = 0; rowC < imgHeight ; ++rowC )
216 {
217 int rowU = std::max( 0, rowC - 1 );
218 int rowD = std::min( imgHeight - 1, rowC + 1 );
219
220 pixelValues[1] = decodeElevation( elevPtr[colRowToIndex( 0, rowU )] );
221 pixelValues[4] = decodeElevation( elevPtr[colRowToIndex( 0, rowC )] );
222 pixelValues[7] = decodeElevation( elevPtr[colRowToIndex( 0, rowD )] );
223
224 pixelValues[2] = decodeElevation( elevPtr[colRowToIndex( 1, rowU )] );
225 pixelValues[5] = decodeElevation( elevPtr[colRowToIndex( 1, rowC )] );
226 pixelValues[8] = decodeElevation( elevPtr[colRowToIndex( 1, rowD )] );
227
228
229 for ( int colC = 0; colC < imgWidth ; ++colC )
230 {
231 qgssize centerIndex = colRowToIndex( colC, rowC );
232 int colR = std::min( imgWidth - 1, colC + 1 );
233
234 pixelValues[0] = pixelValues[1];
235 pixelValues[3] = pixelValues[4];
236 pixelValues[6] = pixelValues[7];
237
238 pixelValues[1] = pixelValues[2];
239 pixelValues[4] = pixelValues[5];
240 pixelValues[7] = pixelValues[8];
241
242 pixelValues[2] = decodeElevation( elevPtr[colRowToIndex( colR, rowU )] );
243 pixelValues[5] = decodeElevation( elevPtr[colRowToIndex( colR, rowC )] );
244 pixelValues[8] = decodeElevation( elevPtr[colRowToIndex( colR, rowD )] );
245
246 if ( elevPtr[centerIndex] != 0 )
247 {
248 // This is center cell. Use this in place of nodata neighbors
249 const float x22 = pixelValues[4];
250 if ( x22 == noData )
251 continue;
252
253 const float x11 = ( pixelValues[0] == noData ) ? x22 : pixelValues[0];
254 const float x21 = ( pixelValues[3] == noData ) ? x22 : pixelValues[3];
255 const float x31 = ( pixelValues[6] == noData ) ? x22 : pixelValues[6];
256
257 const float x12 = ( pixelValues[1] == noData ) ? x22 : pixelValues[1];
258 const float x32 = ( pixelValues[7] == noData ) ? x22 : pixelValues[7];
259
260 const float x13 = ( pixelValues[2] == noData ) ? x22 : pixelValues[2];
261 const float x23 = ( pixelValues[5] == noData ) ? x22 : pixelValues[5];
262 const float x33 = ( pixelValues[8] == noData ) ? x22 : pixelValues[8];
263
264 const double derX = static_cast<double>( ( x13 + x23 + x23 + x33 ) - ( x11 + x21 + x21 + x31 ) ) / ( 8 * cellSizeX );
265 const double derY = static_cast<double>( ( x31 + x32 + x32 + x33 ) - ( x11 + x12 + x12 + x13 ) ) / ( 8 * -cellSizeY );
266
267 double shade = 0;
268 if ( !multiDirectional )
269 {
270 // Standard single direction hillshade
271 shade = std::clamp( ( sin_altRadian -
272 ( derY * cos_az_mul_cos_alt_mul_z -
273 derX * sin_az_mul_cos_alt_mul_z ) ) /
274 std::sqrt( 1 + square_z * ( derX * derX + derY * derY ) ),
275 0.0, 1.0 );
276 }
277 else
278 {
279 // Weighted multi direction as in http://pubs.usgs.gov/of/1992/of92-422/of92-422.pdf
280 // Fast formula from GDAL DEM
281 const double xx = derX * derX;
282 const double yy = derY * derY;
283 const double xx_plus_yy = xx + yy;
284 // Flat?
285 if ( xx_plus_yy == 0.0 )
286 {
287 shade = std::clamp( sin_altRadian, 0.0, 1.0 );
288 }
289 else
290 {
291 // ... then the shade value from different azimuth
292 double val225_mul_0_5 = sin_altRadian * 0.5 +
293 ( derX - derY ) * cos225_az_mul_cos_alt_mul_z_mul_0_5;
294 val225_mul_0_5 = ( val225_mul_0_5 <= 0.0 ) ? 0.0 : val225_mul_0_5;
295 double val270_mul_0_5 = sin_altRadian * 0.5 -
296 derX * cos_alt_mul_z * 0.5;
297 val270_mul_0_5 = ( val270_mul_0_5 <= 0.0 ) ? 0.0 : val270_mul_0_5;
298 double val315_mul_0_5 = sin_altRadian * 0.5 +
299 ( derX + derY ) * cos225_az_mul_cos_alt_mul_z_mul_0_5;
300 val315_mul_0_5 = ( val315_mul_0_5 <= 0.0 ) ? 0.0 : val315_mul_0_5;
301 double val360_mul_0_5 = sin_altRadian * 0.5 -
302 derY * cos_alt_mul_z * 0.5;
303 val360_mul_0_5 = ( val360_mul_0_5 <= 0.0 ) ? 0.0 : val360_mul_0_5;
304
305 // ... then the weighted shading
306 const double weight_225 = 0.5 * xx_plus_yy - derX * derY;
307 const double weight_270 = xx;
308 const double weight_315 = xx_plus_yy - weight_225;
309 const double weight_360 = yy;
310 const double cang_mul_127 = (
311 ( weight_225 * val225_mul_0_5 +
312 weight_270 * val270_mul_0_5 +
313 weight_315 * val315_mul_0_5 +
314 weight_360 * val360_mul_0_5 ) / xx_plus_yy ) /
315 ( 1 + square_z * xx_plus_yy );
316
317 shade = std::clamp( cang_mul_127, 0.0, 1.0 );
318 }
319 }
320
321 QRgb c = imgPtr[ centerIndex ];
322 imgPtr[ centerIndex ] = qRgba( static_cast<int>( static_cast<float>( qRed( c ) ) * shade ),
323 static_cast<int>( static_cast<float>( qGreen( c ) ) * shade ),
324 static_cast<int>( static_cast<float>( qBlue( c ) ) * shade )
325 , qAlpha( c ) );
326 }
327 }
328 }
329}
330
332{
333 if ( !mPainter )
334 {
335 mPainter = std::make_unique<QPainter>( );
336 mPainter->begin( &mElevationImage );
337 }
338 return mPainter.get();
339
340}
341
343{
344 if ( otherElevationMap.mElevationImage.size() != mElevationImage.size() )
345 {
346 QgsDebugMsgLevel( u"Elevation map with different sizes can not be combined"_s, 4 );
347 return;
348 }
349
350 QRgb *elevPtr = reinterpret_cast<QRgb *>( mElevationImage.bits() );
351 const QRgb *otherElevPtr = reinterpret_cast<const QRgb *>( otherElevationMap.mElevationImage.constBits() );
352
353 int width = mElevationImage.width();
354 int height = mElevationImage.height();
355
356 for ( int row = 0; row < height; ++row )
357 {
358 for ( int col = 0; col < width; ++col )
359 {
360 qgssize index = col + static_cast<qgssize>( row ) * width ;
361 if ( !isNoData( otherElevPtr[index] ) )
362 {
363 switch ( method )
364 {
366 {
367 double elev = decodeElevation( elevPtr[index] );
368 double otherElev = decodeElevation( otherElevPtr[index] );
369 if ( otherElev > elev )
370 elevPtr[index] = otherElevPtr[index];
371 }
372 break;
374 elevPtr[index] = otherElevPtr[index];
375 break;
376 }
377 }
378 }
379 }
380}
381
382
384{
385 return !mElevationImage.isNull();
386}
387
388void QgsElevationMap::fillWithRasterBlock( QgsRasterBlock *block, int top, int left, double zScale, double offset )
389{
390 QRgb *dataPtr = reinterpret_cast<QRgb *>( mElevationImage.bits() );
391
392 int widthMap = mElevationImage.width();
393 int heightMap = mElevationImage.height();
394 int widthBlock = block->width();
395 int combinedHeight = std::min( mElevationImage.height(), block->height() + top );
396 int combinedWidth = std::min( widthMap, widthBlock + left );
397 for ( int row = std::max( 0, top ); row < combinedHeight; ++row )
398 {
399 if ( row >= heightMap )
400 continue;
401
402 for ( int col = std::max( 0, left ); col < combinedWidth; ++col )
403 {
404 if ( col >= widthMap )
405 continue;
406
407 bool isNoData = true;
408 double value = block->valueAndNoData( ( row - top ), ( col - left ), isNoData );
409 qgssize index = col + static_cast<qgssize>( row ) * widthMap;
410 if ( isNoData )
411 dataPtr[index] = 0;
412 else
413 dataPtr[index] = encodeElevation( static_cast<float>( value * zScale + offset ) );
414 }
415 }
416}
ElevationMapCombineMethod
Methods used to select the elevation when two elevation maps are combined.
Definition qgis.h:4987
@ NewerElevation
Keep the new elevation regardless of its value if it is not null.
Definition qgis.h:4989
@ HighestElevation
Keep the highest elevation if it is not null.
Definition qgis.h:4988
void combine(const QgsElevationMap &otherElevationMap, Qgis::ElevationMapCombineMethod method)
Combines this elevation map with otherElevationMap.
static QRgb encodeElevation(float z)
Converts elevation value to an actual color.
QgsElevationMap()=default
void applyEyeDomeLighting(QImage &image, int distance, float strength, float rendererScale) const
Applies eye dome lighting effect to the given image.
static std::unique_ptr< QgsElevationMap > fromRasterBlock(QgsRasterBlock *block)
Creates an elevation map based on data from the given raster block.
QPainter * painter() const
Returns painter to the underlying QImage with elevations.
float noDataValue() const
Returns the no data value for the elevation map.
bool isNoData(QRgb colorRaw) const
Returns whether the encoded value is a no data value.
bool isValid() const
Returns whether the elevation map is valid.
static float decodeElevation(QRgb colorRaw)
Converts a color back to elevation value.
void fillWithRasterBlock(QgsRasterBlock *block, int top, int left, double zScale=1.0, double offset=0.0)
Fills the elevation map with values contains in a raster block starting from position defined by top ...
void applyHillshading(QImage &image, bool multiDirectional, double altitude, double azimuth, double zFactor, double cellSizeX, double cellSizeY) const
Applies hill shading effect to the given image.
QgsElevationMap & operator=(const QgsElevationMap &other)
Raster data container.
int height() const
Returns the height (number of rows) of the raster block.
double valueAndNoData(int row, int column, bool &isNoData) const
Reads a single value from the pixel at row and column, if type of block is numeric.
int width() const
Returns the width (number of columns) of the raster block.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:7423
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63