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