QGIS API Documentation 4.3.0-Master (4ecdf0f3f59)
Loading...
Searching...
No Matches
qgsrastervectorfieldvaluesource.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrastervectorfieldvaluesource.cpp
3 -----------------------------------
4 begin : September 2026
5 copyright : (C) 2026 by Stefanos Natsis
6 email : uclaros 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
19
20#include <algorithm>
21#include <cmath>
22
23#include "qgsrasterblock.h"
24
26
27static const double sNoValue = std::numeric_limits<double>::quiet_NaN();
28
30static double cellValue( const QgsRasterBlock *block, int row, int column )
31{
32 if ( !block || row < 0 || column < 0 || row >= block->height() || column >= block->width() )
33 return sNoValue;
34
35 bool isNoData = false;
36 const double value = block->valueAndNoData( row, column, isNoData );
37 if ( isNoData || std::isnan( value ) )
38 return sNoValue;
39
40 return value;
41}
42
43
44//
45// QgsRasterVectorFieldValueSourceBase
46//
47
48QgsRasterVectorFieldValueSourceBase::QgsRasterVectorFieldValueSourceBase( const QgsRectangle &blockExtent, const QgsRectangle &dataExtent, int columns, int rows )
49 : mBlockExtent( blockExtent )
50 , mDataExtent( dataExtent )
51 , mColumns( columns )
52 , mRows( rows )
53{
54 if ( mColumns > 0 && mRows > 0 && !mBlockExtent.isEmpty() )
55 {
56 mXResolution = mBlockExtent.width() / mColumns;
57 mYResolution = mBlockExtent.height() / mRows;
58 }
59}
60
61bool QgsRasterVectorFieldValueSourceBase::isValid() const
62{
63 return mColumns > 0 && mRows > 0 && mXResolution > 0 && mYResolution > 0;
64}
65
66QgsRectangle QgsRasterVectorFieldValueSourceBase::extent() const
67{
68 return mDataExtent;
69}
70
71bool QgsRasterVectorFieldValueSourceBase::nativeLayout( QgsPointXY &origin, double &spacingX, double &spacingY ) const
72{
73 if ( !isValid() )
74 return false;
75
76 // one position per cell of the block, placed on its center
77 spacingX = mXResolution;
78 spacingY = mYResolution;
79 origin = QgsPointXY( mBlockExtent.xMinimum() + spacingX / 2, mBlockExtent.yMaximum() - spacingY / 2 );
80 return true;
81}
82
83QVector<QgsPointXY> QgsRasterVectorFieldValueSourceBase::seedPoints( const QgsRectangle &extent ) const
84{
85 QVector<QgsPointXY> points;
86 if ( !isValid() )
87 return points;
88
89 const QgsRectangle seedExtent = mBlockExtent.intersect( extent );
90 if ( seedExtent.isEmpty() )
91 return points;
92
93 // the cells whose center falls within the requested extent
94 const int firstColumn = std::max( 0, static_cast<int>( std::ceil( ( seedExtent.xMinimum() - mBlockExtent.xMinimum() ) / mXResolution - 0.5 ) ) );
95 const int lastColumn = std::min( mColumns - 1, static_cast<int>( std::floor( ( seedExtent.xMaximum() - mBlockExtent.xMinimum() ) / mXResolution - 0.5 ) ) );
96 const int firstRow = std::max( 0, static_cast<int>( std::ceil( ( mBlockExtent.yMaximum() - seedExtent.yMaximum() ) / mYResolution - 0.5 ) ) );
97 const int lastRow = std::min( mRows - 1, static_cast<int>( std::floor( ( mBlockExtent.yMaximum() - seedExtent.yMinimum() ) / mYResolution - 0.5 ) ) );
98
99 if ( firstColumn > lastColumn || firstRow > lastRow )
100 return points;
101
102 // a fine raster holds far more cells than can usefully seed a trace, so thin them out evenly
103 const qint64 candidates = static_cast<qint64>( lastColumn - firstColumn + 1 ) * ( lastRow - firstRow + 1 );
104 int step = 1;
105 if ( candidates > MAXIMUM_SEED_POINTS )
106 step = static_cast<int>( std::ceil( std::sqrt( static_cast<double>( candidates ) / MAXIMUM_SEED_POINTS ) ) );
107
108 points.reserve( static_cast<int>( candidates / ( static_cast<qint64>( step ) * step ) ) + 1 );
109 for ( int row = firstRow; row <= lastRow; row += step )
110 {
111 const double y = mBlockExtent.yMaximum() - ( row + 0.5 ) * mYResolution;
112 for ( int column = firstColumn; column <= lastColumn; column += step )
113 {
114 if ( !cellHasValue( row, column ) )
115 continue;
116
117 points.append( QgsPointXY( mBlockExtent.xMinimum() + ( column + 0.5 ) * mXResolution, y ) );
118 }
119 }
120
121 return points;
122}
123
124
125//
126// QgsRasterVectorFieldValueSource
127//
128
130static int commonColumns( const std::shared_ptr<const QgsRasterBlock> &first, const std::shared_ptr<const QgsRasterBlock> &second )
131{
132 return first && second ? std::min( first->width(), second->width() ) : 0;
133}
134
136static int commonRows( const std::shared_ptr<const QgsRasterBlock> &first, const std::shared_ptr<const QgsRasterBlock> &second )
137{
138 return first && second ? std::min( first->height(), second->height() ) : 0;
139}
140
141QgsRasterVectorFieldValueSource::QgsRasterVectorFieldValueSource(
142 std::shared_ptr<const QgsRasterBlock> xValues, std::shared_ptr<const QgsRasterBlock> yValues, const QgsRectangle &blockExtent, const QgsRectangle &dataExtent, double maximumMagnitude
143)
144 : QgsRasterVectorFieldValueSourceBase( blockExtent, dataExtent, commonColumns( xValues, yValues ), commonRows( xValues, yValues ) )
145 , mXValues( std::move( xValues ) )
146 , mYValues( std::move( yValues ) )
147 , mMaximumMagnitude( maximumMagnitude )
148{}
149
150bool QgsRasterVectorFieldValueSource::cellHasValue( int row, int column ) const
151{
152 return !std::isnan( cellValue( mXValues.get(), row, column ) ) && !std::isnan( cellValue( mYValues.get(), row, column ) );
153}
154
155QgsRasterVectorFieldValueSource *QgsRasterVectorFieldValueSource::clone() const
156{
157 // the blocks are shared and never written to, so the copy is cheap
158 return new QgsRasterVectorFieldValueSource( *this );
159}
160
161double QgsRasterVectorFieldValueSource::sampledValue( const QgsRasterBlock *block, const QgsPointXY &point ) const
162{
163 // position of the point in cell units, measured from the center of the top left cell
164 const double x = ( point.x() - mBlockExtent.xMinimum() ) / mXResolution - 0.5;
165 const double y = ( mBlockExtent.yMaximum() - point.y() ) / mYResolution - 0.5;
166
167 const int column = static_cast<int>( std::floor( x ) );
168 const int row = static_cast<int>( std::floor( y ) );
169
170 const double v00 = cellValue( block, row, column );
171 const double v01 = cellValue( block, row, column + 1 );
172 const double v10 = cellValue( block, row + 1, column );
173 const double v11 = cellValue( block, row + 1, column + 1 );
174
175 if ( std::isnan( v00 ) || std::isnan( v01 ) || std::isnan( v10 ) || std::isnan( v11 ) )
176 {
177 // at the edges of the data, and around nodata cells, fall back to the nearest cell so that the
178 // last row and column of the raster still carry values
179 return cellValue( block, static_cast<int>( std::floor( y + 0.5 ) ), static_cast<int>( std::floor( x + 0.5 ) ) );
180 }
181
182 const double dx = x - column;
183 const double dy = y - row;
184
185 return v00 * ( 1 - dx ) * ( 1 - dy ) + v01 * dx * ( 1 - dy ) + v10 * ( 1 - dx ) * dy + v11 * dx * dy;
186}
187
188QgsVector QgsRasterVectorFieldValueSource::vectorValue( const QgsPointXY &point ) const
189{
190 if ( !isValid() || !mBlockExtent.contains( point ) )
191 return QgsVector( sNoValue, sNoValue );
192
193 const double x = sampledValue( mXValues.get(), point );
194 const double y = sampledValue( mYValues.get(), point );
195 if ( std::isnan( x ) || std::isnan( y ) )
196 return QgsVector( sNoValue, sNoValue );
197
198 return QgsVector( x, y );
199}
200
201double QgsRasterVectorFieldValueSource::maximumMagnitude() const
202{
203 return mMaximumMagnitude;
204}
205
206std::unique_ptr<QgsRasterInterface> QgsRasterVectorFieldValueSource::magnitudeSource( const QgsRenderContext &, QSize ) const
207{
208 if ( !isValid() )
209 return nullptr;
210
211 return std::make_unique<QgsRasterVectorFieldMagnitudeInterface>( mXValues, mYValues, mBlockExtent );
212}
213
214
215//
216// QgsRasterVectorFieldEncodedDirectionValueSource
217//
218
219QgsRasterVectorFieldEncodedDirectionValueSource::QgsRasterVectorFieldEncodedDirectionValueSource(
220 std::shared_ptr<const QgsRasterBlock> values, Qgis::RasterDirectionEncoding encoding, const QgsRectangle &blockExtent, const QgsRectangle &dataExtent
221)
222 : QgsRasterVectorFieldValueSourceBase( blockExtent, dataExtent, values ? values->width() : 0, values ? values->height() : 0 )
223 , mValues( std::move( values ) )
224 , mEncoding( encoding )
225{}
226
227QgsRasterVectorFieldEncodedDirectionValueSource *QgsRasterVectorFieldEncodedDirectionValueSource::clone() const
228{
229 // the block is shared and never written to, so the copy is cheap
230 return new QgsRasterVectorFieldEncodedDirectionValueSource( *this );
231}
232
233int QgsRasterVectorFieldEncodedDirectionValueSource::directionOrdinal( Qgis::RasterDirectionEncoding encoding, double value )
234{
235 if ( std::isnan( value ) )
236 return -1;
237
238 // the encodings are all integer codes, a value between two of them names no direction
239 const double rounded = std::round( value );
240 if ( std::abs( value - rounded ) > 1e-6 || std::abs( rounded ) > std::numeric_limits<int>::max() )
241 return -1;
242
243 int code = static_cast<int>( rounded );
244
245 switch ( encoding )
246 {
248 // 32 64 128
249 // 16 1
250 // 8 4 2
251 switch ( code )
252 {
253 case 64:
254 return 0; // N
255 case 128:
256 return 1; // NE
257 case 1:
258 return 2; // E
259 case 2:
260 return 3; // SE
261 case 4:
262 return 4; // S
263 case 8:
264 return 5; // SW
265 case 16:
266 return 6; // W
267 case 32:
268 return 7; // NW
269 default:
270 return -1;
271 }
272
274 // 3 2 1
275 // 4 8
276 // 5 6 7
277 code = std::abs( code );
278 switch ( code )
279 {
280 case 2:
281 return 0; // N
282 case 1:
283 return 1; // NE
284 case 8:
285 return 2; // E
286 case 7:
287 return 3; // SE
288 case 6:
289 return 4; // S
290 case 5:
291 return 5; // SW
292 case 4:
293 return 6; // W
294 case 3:
295 return 7; // NW
296 default:
297 return -1;
298 }
299
301 // clockwise from north, which is the ordinal itself
302 // 7 0 1
303 // 6 2
304 // 5 4 3
305 return code >= 0 && code <= 7 ? code : -1;
306
308 // 7 8 9
309 // 4 6
310 // 1 2 3
311 switch ( code )
312 {
313 case 8:
314 return 0; // N
315 case 9:
316 return 1; // NE
317 case 6:
318 return 2; // E
319 case 3:
320 return 3; // SE
321 case 2:
322 return 4; // S
323 case 1:
324 return 5; // SW
325 case 4:
326 return 6; // W
327 case 7:
328 return 7; // NW
329 default:
330 return -1;
331 }
332 }
333
334 return -1;
335}
336
337QgsVector QgsRasterVectorFieldEncodedDirectionValueSource::ordinalVector( int ordinal )
338{
339 constexpr double D = M_SQRT1_2;
340 // clockwise from north, north up
341 switch ( ordinal )
342 {
343 case 0:
344 return QgsVector( 0, 1 ); // N
345 case 1:
346 return QgsVector( D, D ); // NE
347 case 2:
348 return QgsVector( 1, 0 ); // E
349 case 3:
350 return QgsVector( D, -D ); // SE
351 case 4:
352 return QgsVector( 0, -1 ); // S
353 case 5:
354 return QgsVector( -D, -D ); // SW
355 case 6:
356 return QgsVector( -1, 0 ); // W
357 case 7:
358 return QgsVector( -D, D ); // NW
359 default:
360 return QgsVector( sNoValue, sNoValue );
361 };
362}
363
364void QgsRasterVectorFieldEncodedDirectionValueSource::setSamplingWindow( double width, double height )
365{
366 mWindowWidth = std::max( 0.0, width );
367 mWindowHeight = std::max( 0.0, height );
368}
369
370bool QgsRasterVectorFieldEncodedDirectionValueSource::cellHasValue( int row, int column ) const
371{
372 return directionOrdinal( mEncoding, cellValue( mValues.get(), row, column ) ) >= 0;
373}
374
375int QgsRasterVectorFieldEncodedDirectionValueSource::nearestOrdinal( const QgsPointXY &point ) const
376{
377 const int column = static_cast<int>( std::floor( ( point.x() - mBlockExtent.xMinimum() ) / mXResolution ) );
378 const int row = static_cast<int>( std::floor( ( mBlockExtent.yMaximum() - point.y() ) / mYResolution ) );
379
380 return directionOrdinal( mEncoding, cellValue( mValues.get(), row, column ) );
381}
382
383int QgsRasterVectorFieldEncodedDirectionValueSource::majorityOrdinal( const QgsPointXY &point ) const
384{
385 // the cells whose centers fall within the window centered on the point
386 const double xMinimum = point.x() - mWindowWidth / 2;
387 const double xMaximum = point.x() + mWindowWidth / 2;
388 const double yMinimum = point.y() - mWindowHeight / 2;
389 const double yMaximum = point.y() + mWindowHeight / 2;
390
391 const int firstColumn = std::max( 0, static_cast<int>( std::ceil( ( xMinimum - mBlockExtent.xMinimum() ) / mXResolution - 0.5 ) ) );
392 const int lastColumn = std::min( mColumns - 1, static_cast<int>( std::floor( ( xMaximum - mBlockExtent.xMinimum() ) / mXResolution - 0.5 ) ) );
393 const int firstRow = std::max( 0, static_cast<int>( std::ceil( ( mBlockExtent.yMaximum() - yMaximum ) / mYResolution - 0.5 ) ) );
394 const int lastRow = std::min( mRows - 1, static_cast<int>( std::floor( ( mBlockExtent.yMaximum() - yMinimum ) / mYResolution - 0.5 ) ) );
395
396 // a window narrower than a cell can fall between two cell centers, fall back to the cell it is in
397 if ( firstColumn > lastColumn || firstRow > lastRow )
398 return nearestOrdinal( point );
399
400 int counts[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
401 int best = 0;
402 QgsVector resultant( 0, 0 );
403
404 for ( int row = firstRow; row <= lastRow; ++row )
405 {
406 for ( int column = firstColumn; column <= lastColumn; ++column )
407 {
408 const int ordinal = directionOrdinal( mEncoding, cellValue( mValues.get(), row, column ) );
409 if ( ordinal < 0 )
410 continue;
411
412 best = std::max( best, ++counts[ordinal] );
413 resultant += ordinalVector( ordinal );
414 }
415 }
416
417 if ( best == 0 )
418 return -1; // the window holds no direction at all
419
420 // several directions can be equally common, in which case the one closest to the resultant of the
421 // whole window is the most representative. The lowest ordinal breaks a resultant which cancels out
422 int majority = -1;
423 double bestProjection = std::numeric_limits<double>::lowest();
424 for ( int ordinal = 0; ordinal < 8; ++ordinal )
425 {
426 if ( counts[ordinal] != best )
427 continue;
428
429 const double projection = resultant * ordinalVector( ordinal );
430 if ( projection > bestProjection )
431 {
432 bestProjection = projection;
433 majority = ordinal;
434 }
435 }
436
437 return majority;
438}
439
440QgsVector QgsRasterVectorFieldEncodedDirectionValueSource::vectorValue( const QgsPointXY &point ) const
441{
442 if ( !isValid() || !mBlockExtent.contains( point ) )
443 return QgsVector( sNoValue, sNoValue );
444
445 const bool hasWindow = mWindowWidth > 0 && mWindowHeight > 0;
446 return ordinalVector( hasWindow ? majorityOrdinal( point ) : nearestOrdinal( point ) );
447}
448
449double QgsRasterVectorFieldEncodedDirectionValueSource::maximumMagnitude() const
450{
451 return 1.0;
452}
453
454
455//
456// QgsRasterVectorFieldMagnitudeInterface
457//
458
459QgsRasterVectorFieldMagnitudeInterface::QgsRasterVectorFieldMagnitudeInterface( std::shared_ptr<const QgsRasterBlock> xValues, std::shared_ptr<const QgsRasterBlock> yValues, const QgsRectangle &blockExtent )
460 : mXValues( std::move( xValues ) )
461 , mYValues( std::move( yValues ) )
462 , mBlockExtent( blockExtent )
463{}
464
465QgsRasterInterface *QgsRasterVectorFieldMagnitudeInterface::clone() const
466{
467 return new QgsRasterVectorFieldMagnitudeInterface( mXValues, mYValues, mBlockExtent );
468}
469
470Qgis::DataType QgsRasterVectorFieldMagnitudeInterface::dataType( int ) const
471{
473}
474
475int QgsRasterVectorFieldMagnitudeInterface::bandCount() const
476{
477 return 1;
478}
479
480QgsRectangle QgsRasterVectorFieldMagnitudeInterface::extent() const
481{
482 return mBlockExtent;
483}
484
485QgsRasterBlock *QgsRasterVectorFieldMagnitudeInterface::block( int, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
486{
487 auto outputBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Float64, width, height );
488 // the color ramp shader must see the holes in the data, which are encoded as NaN
489 outputBlock->setNoDataValue( std::numeric_limits<double>::quiet_NaN() );
490
491 if ( !mXValues || !mYValues || width <= 0 || height <= 0 || extent.isEmpty() || mBlockExtent.isEmpty() )
492 return outputBlock.release();
493
494 const int columns = std::min( mXValues->width(), mYValues->width() );
495 const int rows = std::min( mXValues->height(), mYValues->height() );
496 if ( columns <= 0 || rows <= 0 )
497 return outputBlock.release();
498
499 // the requested extent and size are those of the streamline field, which generally match neither
500 // the extent nor the resolution of the cached blocks, so resample by geographic position
501 const double xResolution = mBlockExtent.width() / columns;
502 const double yResolution = mBlockExtent.height() / rows;
503 const double outputXResolution = extent.width() / width;
504 const double outputYResolution = extent.height() / height;
505
506 for ( int row = 0; row < height; ++row )
507 {
508 if ( feedback && feedback->isCanceled() )
509 break;
510
511 const double y = extent.yMaximum() - ( row + 0.5 ) * outputYResolution;
512 const int sourceRow = static_cast<int>( std::floor( ( mBlockExtent.yMaximum() - y ) / yResolution ) );
513
514 for ( int column = 0; column < width; ++column )
515 {
516 const double x = extent.xMinimum() + ( column + 0.5 ) * outputXResolution;
517 const int sourceColumn = static_cast<int>( std::floor( ( x - mBlockExtent.xMinimum() ) / xResolution ) );
518
519 const double xValue = cellValue( mXValues.get(), sourceRow, sourceColumn );
520 const double yValue = cellValue( mYValues.get(), sourceRow, sourceColumn );
521
522 outputBlock->setValue( row, column, std::hypot( xValue, yValue ) );
523 }
524 }
525
526 return outputBlock.release();
527}
528
DataType
Raster data types.
Definition qgis.h:413
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:422
RasterDirectionEncoding
Schemes used to encode a compass direction as an integer raster value.
Definition qgis.h:7276
@ PcRaster
PCRaster: 1=SW, 2=S, 3=SE, 4=W, 6=E, 7=NW, 8=N, 9=NE.
Definition qgis.h:7280
@ Esri
Esri: 1=E, 2=SE, 4=S, 8=SW, 16=W, 32=NW, 64=N, 128=NE.
Definition qgis.h:7277
@ Saga
SAGA: 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW.
Definition qgis.h:7279
@ Grass
GRASS: 1=NE, 2=N, 3=NW, 4=W, 5=SW, 6=S, 7=SE, 8=E.
Definition qgis.h:7278
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Feedback object tailored for raster block reading.
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.
Base class for processing filters like renderers, reprojector, resampler etc.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Contains information about the context of a rendering operation.
Represent a 2-dimensional vector.
Definition qgsvector.h:34