QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgsmesh3daveraging.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmesh3daveraging.cpp
3 ----------------------
4 begin : November 2019
5 copyright : (C) 2019 by Peter Petrik
6 email : zilolv 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 "qgsmesh3daveraging.h"
19
20#include <memory>
21
22#include "qgsfeedback.h"
23#include "qgsmeshdataprovider.h"
25
26// threshold for length intervals, to avoid division by 0
27static const double eps = 1e-6;
28
33
35{
36 std::unique_ptr<QgsMesh3DAveragingMethod> ret;
37
39 elem.attribute( QStringLiteral( "method" ) ).toInt() );
40 switch ( method )
41 {
43 ret = std::make_unique<QgsMeshMultiLevelsAveragingMethod>( );
44 break;
46 ret = std::make_unique<QgsMeshSigmaAveragingMethod>( );
47 break;
49 ret = std::make_unique<QgsMeshRelativeHeightAveragingMethod>( );
50 break;
52 ret = std::make_unique<QgsMeshElevationAveragingMethod>( );
53 break;
54 }
55 ret->readXml( elem );
56 return ret.release();
57}
58
60{
61 if ( !block3d.isValid() )
62 return QgsMeshDataBlock();
63
64 if ( !hasValidInputs() )
65 return QgsMeshDataBlock();
66
67 const bool isVector = block3d.isVector();
68 const int count = block3d.count();
70 QVector<double> valuesFaces( isVector ? 2 * count : count, std::numeric_limits<double>::quiet_NaN() );
71 const QVector<int> verticalLevelsCount = block3d.verticalLevelsCount();
72 const QVector<double> verticalLevels = block3d.verticalLevels();
73 const QVector<double> volumeValues = block3d.values();
74
75 int startVolumeIndex = 0;
76 for ( int faceIndex = 0; faceIndex < count; ++faceIndex )
77 {
78 if ( feedback && feedback->isCanceled() )
79 {
80 return QgsMeshDataBlock();
81 }
82
83 const int volumesBelowFaceCount = verticalLevelsCount[faceIndex];
84 if ( volumesBelowFaceCount <= 0 )
85 continue;
86
87 const int startVerticalLevelIndex = startVolumeIndex + faceIndex;
88 Q_ASSERT( verticalLevels.size() >= startVerticalLevelIndex + volumesBelowFaceCount + 1 );
89 QVector<double> verticalLevelsForFace = verticalLevels.mid( startVerticalLevelIndex, volumesBelowFaceCount + 1 );
90 double faceLevelTop = verticalLevelsForFace[0];
91 double faceLevelBottom = verticalLevelsForFace[verticalLevelsForFace.size() - 1];
92
93 // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
94 if ( faceLevelTop < faceLevelBottom )
95 {
96 std::swap( faceLevelTop, faceLevelBottom );
97 }
98
99 double methodLevelTop = std::numeric_limits<double>::quiet_NaN();
100 double methodLevelBottom = std::numeric_limits<double>::quiet_NaN();
101
102 int singleVerticalIndex = -1;
103 volumeRangeForFace( methodLevelTop,
104 methodLevelBottom,
105 singleVerticalIndex,
106 verticalLevelsForFace );
107
108 if ( singleVerticalIndex != -1 )
109 {
110 int volumeIndex = singleVerticalIndex + startVolumeIndex;
111 if ( isVector )
112 {
113 valuesFaces[2 * faceIndex] = volumeValues.at( 2 * volumeIndex );
114 valuesFaces[2 * faceIndex + 1 ] = volumeValues.at( 2 * volumeIndex + 1 );
115 }
116 else
117 {
118 valuesFaces[faceIndex] = volumeValues.at( volumeIndex );
119 }
120 }
121 else if ( !std::isnan( methodLevelTop ) && !std::isnan( methodLevelBottom ) )
122 {
123 // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
124 if ( methodLevelTop < methodLevelBottom )
125 {
126 std::swap( methodLevelTop, methodLevelBottom );
127 }
128
129 // check if we are completely outside the limits
130 if ( ( methodLevelTop >= faceLevelBottom ) && ( methodLevelBottom <= faceLevelTop ) )
131 {
132 averageVolumeValuesForFace(
133 faceIndex,
134 volumesBelowFaceCount,
135 startVolumeIndex,
136 methodLevelTop,
137 methodLevelBottom,
138 isVector,
139 verticalLevelsForFace,
140 volumeValues,
141 valuesFaces
142 );
143 }
144 }
145
146 // move to next face and associated volumes
147 startVolumeIndex += volumesBelowFaceCount;
148 }
149 result.setValues( valuesFaces );
150 return result;
151}
152
157
158void QgsMesh3DAveragingMethod::averageVolumeValuesForFace(
159 int faceIndex,
160 int volumesBelowFaceCount,
161 int startVolumeIndex,
162 double methodLevelTop,
163 double methodLevelBottom,
164 bool isVector,
165 const QVector<double> &verticalLevelsForFace,
166 const QVector<double> &volumeValues,
167 QVector<double> &valuesFaces
168) const
169{
170 double totalAveragedHeight = 0;
171 double nSumX = 0.0;
172 double nSumY = 0.0;
173
174 // Now go through all volumes below face and check if we need to take that volume into consideration
175 for ( int relativeVolumeIndex = 0; relativeVolumeIndex < volumesBelowFaceCount; ++relativeVolumeIndex )
176 {
177 const int volumeIndex = startVolumeIndex + relativeVolumeIndex;
178 double volumeLevelTop = verticalLevelsForFace[relativeVolumeIndex];
179 double volumeLevelBottom = verticalLevelsForFace[relativeVolumeIndex + 1];
180 if ( volumeLevelTop < volumeLevelBottom )
181 {
182 std::swap( volumeLevelTop, volumeLevelBottom );
183 }
184
185 const double intersectionLevelTop = std::min( methodLevelTop, volumeLevelTop );
186 const double intersectionLevelBottom = std::max( methodLevelBottom, volumeLevelBottom );
187 const double effectiveInterval = intersectionLevelTop - intersectionLevelBottom;
188
189 if ( effectiveInterval > eps )
190 {
191 if ( isVector )
192 {
193 const double x = volumeValues[2 * volumeIndex ];
194 const double y = volumeValues[ 2 * volumeIndex + 1 ];
195 if ( ! std::isnan( x ) &&
196 ! std::isnan( y )
197 )
198 {
199 nSumX += x * effectiveInterval;
200 nSumY += y * effectiveInterval;
201 totalAveragedHeight += effectiveInterval;
202 }
203 }
204 else
205 {
206 const double x = volumeValues[ volumeIndex ];
207 if ( ! std::isnan( x ) )
208 {
209 nSumX += x * effectiveInterval;
210 totalAveragedHeight += effectiveInterval;
211 }
212 }
213 }
214 }
215
216 // calculate average
217 if ( totalAveragedHeight > eps )
218 {
219 if ( isVector )
220 {
221 valuesFaces[2 * faceIndex] = nSumX / totalAveragedHeight;
222 valuesFaces[2 * faceIndex + 1 ] = nSumY / totalAveragedHeight;
223 }
224 else
225 {
226 valuesFaces[faceIndex] = nSumX / totalAveragedHeight;
227 }
228 }
229}
230
232{
233 if ( a )
234 return a->equals( b );
235 else
236 return !b;
237}
238
241 , mStartVerticalLevel( startLevel )
242 , mEndVerticalLevel( endLevel )
243 , mCountedFromTop( countedFromTop )
244{
245 if ( mStartVerticalLevel > mEndVerticalLevel )
246 {
247 std::swap( mStartVerticalLevel, mEndVerticalLevel );
248 }
249}
250
255
258 , mStartVerticalLevel( verticalLevel )
259 , mEndVerticalLevel( verticalLevel )
260 , mCountedFromTop( countedFromTop )
261{
262}
263
265
266QDomElement QgsMeshMultiLevelsAveragingMethod::writeXml( QDomDocument &doc ) const
267{
268 QDomElement elem = doc.createElement( QStringLiteral( "multi-vertical-layers-settings" ) );
269 elem.setAttribute( QStringLiteral( "start-layer-index" ), startVerticalLevel() );
270 elem.setAttribute( QStringLiteral( "end-layer-index" ), endVerticalLevel() );
271 return elem;
272}
273
274void QgsMeshMultiLevelsAveragingMethod::readXml( const QDomElement &elem )
275{
276 const QDomElement settings = elem.firstChildElement( QStringLiteral( "multi-vertical-layers-settings" ) );
277 if ( !settings.isNull() )
278 {
279 mStartVerticalLevel = settings.attribute( QStringLiteral( "start-layer-index" ) ).toInt();
280 mEndVerticalLevel = settings.attribute( QStringLiteral( "end-layer-index" ) ).toInt();
281 if ( mStartVerticalLevel > mEndVerticalLevel )
282 {
283 std::swap( mStartVerticalLevel, mEndVerticalLevel );
284 }
285 }
286}
287
289{
290 if ( !other || other->method() != method() )
291 return false;
292
293 const QgsMeshMultiLevelsAveragingMethod *otherMethod = static_cast<const QgsMeshMultiLevelsAveragingMethod *>( other );
294
295 return ( otherMethod->startVerticalLevel() == startVerticalLevel() ) &&
296 ( otherMethod->endVerticalLevel() == endVerticalLevel() ) &&
297 ( otherMethod->countedFromTop() == countedFromTop() );
298}
299
304
305
307{
308 return mStartVerticalLevel;
309}
310
312{
313 return mEndVerticalLevel;
314}
315
316bool QgsMeshMultiLevelsAveragingMethod::hasValidInputs() const
317{
318 return mStartVerticalLevel >= 1 && mEndVerticalLevel >= mStartVerticalLevel;
319}
320
321void QgsMeshMultiLevelsAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
322 double &endVerticalLevel,
323 int &singleVerticalIndex,
324 const QVector<double> &verticalLevels ) const
325{
326 Q_ASSERT( mStartVerticalLevel <= mEndVerticalLevel );
327
328 if ( countedFromTop() )
329 {
330 const int startIndex = mStartVerticalLevel - 1;
331 if ( mStartVerticalLevel == mEndVerticalLevel )
332 {
333 if ( startIndex >= 0 && startIndex < verticalLevels.size() - 1 )
334 singleVerticalIndex = startIndex;
335 }
336 else
337 {
338 if ( startIndex >= 0 && startIndex < verticalLevels.size() )
339 {
340 startVerticalLevel = verticalLevels[ startIndex ];
341 }
342
343 if ( mEndVerticalLevel >= 0 && mEndVerticalLevel < verticalLevels.size() )
344 {
345 endVerticalLevel = verticalLevels[ mEndVerticalLevel ];
346 }
347 else
348 {
349 endVerticalLevel = verticalLevels[ verticalLevels.size() - 1 ];
350 }
351 }
352 }
353 else
354 {
355 const int volumesBelowFaceCount = verticalLevels.size() - 1;
356 const int startIndex = volumesBelowFaceCount - mEndVerticalLevel;
357 if ( mStartVerticalLevel == mEndVerticalLevel )
358 {
359 if ( startIndex >= 0 && startIndex < verticalLevels.size() - 1 )
360 singleVerticalIndex = startIndex;
361 }
362 else
363 {
364 if ( startIndex >= 0 && startIndex < verticalLevels.size() )
365 {
366 startVerticalLevel = verticalLevels[ startIndex ];
367 }
368 else
369 {
370 startVerticalLevel = verticalLevels[ 0 ];
371 }
372
373 const int endIndex = volumesBelowFaceCount - mStartVerticalLevel + 1;
374 if ( endIndex >= 0 && endIndex < verticalLevels.size() )
375 {
376 endVerticalLevel = verticalLevels[ endIndex ];
377 }
378 }
379 }
380}
381
386
389 , mStartFraction( startFraction )
390 , mEndFraction( endFraction )
391{
392 if ( mStartFraction > mEndFraction )
393 {
394 std::swap( mStartFraction, mEndFraction );
395 }
396}
397
399
400QDomElement QgsMeshSigmaAveragingMethod::writeXml( QDomDocument &doc ) const
401{
402 QDomElement elem = doc.createElement( QStringLiteral( "sigma-settings" ) );
403 elem.setAttribute( QStringLiteral( "start-fraction" ), startFraction() );
404 elem.setAttribute( QStringLiteral( "end-fraction" ), endFraction() );
405 return elem;
406}
407
408void QgsMeshSigmaAveragingMethod::readXml( const QDomElement &elem )
409{
410 const QDomElement settings = elem.firstChildElement( QStringLiteral( "sigma-settings" ) );
411 if ( !settings.isNull() )
412 {
413 mStartFraction = settings.attribute( QStringLiteral( "start-fraction" ) ).toDouble();
414 mEndFraction = settings.attribute( QStringLiteral( "end-fraction" ) ).toDouble();
415 if ( mStartFraction > mEndFraction )
416 {
417 std::swap( mStartFraction, mEndFraction );
418 }
419 }
420}
421
423{
424 if ( !other || other->method() != method() )
425 return false;
426
427 const QgsMeshSigmaAveragingMethod *otherMethod = static_cast<const QgsMeshSigmaAveragingMethod *>( other );
428
429 return qgsDoubleNear( otherMethod->startFraction(), startFraction() ) && qgsDoubleNear( otherMethod->endFraction(), endFraction() ) ;
430}
431
436
438{
439 return mStartFraction;
440}
441
443{
444 return mEndFraction;
445}
446
447bool QgsMeshSigmaAveragingMethod::hasValidInputs() const
448{
449 return mStartFraction >= 0 && mEndFraction >= mStartFraction && mEndFraction <= 1;
450}
451
452void QgsMeshSigmaAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
453 double &endVerticalLevel,
454 int &,
455 const QVector<double> &verticalLevels ) const
456{
457 const double top = verticalLevels[ 0 ];
458 const double bot = verticalLevels[ verticalLevels.size() - 1 ];
459 const double diff = top - bot;
460
461 if ( mStartFraction < 0 )
462 startVerticalLevel = bot;
463 else
464 startVerticalLevel = bot + diff * mStartFraction;
465
466 if ( mEndFraction > 1 )
467 endVerticalLevel = top;
468 else
469 endVerticalLevel = bot + diff * mEndFraction;
470}
471
473{
474 return mCountedFromTop;
475}
476
478{
479 return mStartVerticalLevel == mEndVerticalLevel;
480}
481
482
487
490 , mStartHeight( startDepth )
491 , mEndHeight( endDepth )
492 , mCountedFromTop( countedFromTop )
493{
494 if ( mStartHeight > mEndHeight )
495 {
496 std::swap( mStartHeight, mEndHeight );
497 }
498}
499
501
502QDomElement QgsMeshRelativeHeightAveragingMethod::writeXml( QDomDocument &doc ) const
503{
504 QDomElement elem = doc.createElement( QStringLiteral( "relative-height-settings" ) );
505 elem.setAttribute( QStringLiteral( "start-height" ), startHeight() );
506 elem.setAttribute( QStringLiteral( "end-height" ), endHeight() );
507 return elem;
508}
509
511{
512 const QDomElement settings = elem.firstChildElement( QStringLiteral( "relative-height-settings" ) );
513 if ( !settings.isNull() )
514 {
515 mStartHeight = settings.attribute( QStringLiteral( "start-height" ) ).toDouble();
516 mEndHeight = settings.attribute( QStringLiteral( "end-height" ) ).toDouble();
517 if ( mStartHeight > mEndHeight )
518 {
519 std::swap( mStartHeight, mEndHeight );
520 }
521 }
522}
523
525{
526 if ( !other || other->method() != method() )
527 return false;
528
529 const QgsMeshRelativeHeightAveragingMethod *otherMethod = static_cast<const QgsMeshRelativeHeightAveragingMethod *>( other );
530
531 return qgsDoubleNear( otherMethod->startHeight(), startHeight() ) &&
532 qgsDoubleNear( otherMethod->endHeight(), endHeight() ) &&
533 otherMethod->countedFromTop() == countedFromTop();
534}
535
540
542{
543 return mStartHeight;
544}
545
547{
548 return mEndHeight;
549}
550
551bool QgsMeshRelativeHeightAveragingMethod::hasValidInputs() const
552{
553 return mStartHeight >= 0 && mEndHeight >= mStartHeight;
554}
555
556void QgsMeshRelativeHeightAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
557 double &endVerticalLevel,
558 int &,
559 const QVector<double> &verticalLevels ) const
560{
561 if ( countedFromTop() )
562 {
563 const double top = verticalLevels[ 0 ];
564 startVerticalLevel = top - mStartHeight;
565 endVerticalLevel = top - mEndHeight;
566 }
567 else
568 {
569 const double bot = verticalLevels[verticalLevels.size() - 1];
570 startVerticalLevel = bot + mStartHeight;
571 endVerticalLevel = bot + mEndHeight;
572 }
573}
574
576{
577 return mCountedFromTop;
578}
579
584
587 , mStartElevation( startElevation )
588 , mEndElevation( endElevation )
589{
590 if ( mEndElevation > mStartElevation )
591 {
592 std::swap( mEndElevation, mStartElevation );
593 }
594}
595
597
598QDomElement QgsMeshElevationAveragingMethod::writeXml( QDomDocument &doc ) const
599{
600 QDomElement elem = doc.createElement( QStringLiteral( "elevation-settings" ) );
601 elem.setAttribute( QStringLiteral( "start-elevation" ), startElevation() );
602 elem.setAttribute( QStringLiteral( "end-elevation" ), endElevation() );
603 return elem;
604}
605
606void QgsMeshElevationAveragingMethod::readXml( const QDomElement &elem )
607{
608 const QDomElement settings = elem.firstChildElement( QStringLiteral( "elevation-settings" ) );
609 if ( !settings.isNull() )
610 {
611 mStartElevation = settings.attribute( QStringLiteral( "start-elevation" ) ).toDouble();
612 mEndElevation = settings.attribute( QStringLiteral( "end-elevation" ) ).toDouble();
613 if ( mEndElevation > mStartElevation )
614 {
615 std::swap( mEndElevation, mStartElevation );
616 }
617 }
618}
619
621{
622 if ( !other || other->method() != method() )
623 return false;
624
625 const QgsMeshElevationAveragingMethod *otherMethod = static_cast<const QgsMeshElevationAveragingMethod *>( other );
626
627 return qgsDoubleNear( otherMethod->startElevation(), startElevation() ) && qgsDoubleNear( otherMethod->endElevation(), endElevation() ) ;
628}
629
634
636{
637 return mStartElevation;
638}
639
641{
642 return mEndElevation;
643}
644
645bool QgsMeshElevationAveragingMethod::hasValidInputs() const
646{
647 return mStartElevation <= 0.0 && mEndElevation <= mStartElevation;
648}
649
650void QgsMeshElevationAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
651 double &endVerticalLevel,
652 int &,
653 const QVector<double> &verticalLevels ) const
654{
655 Q_UNUSED( verticalLevels )
656 startVerticalLevel = mStartElevation;
657 endVerticalLevel = mEndElevation;
658}
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
static QgsMesh3DAveragingMethod * createFromXml(const QDomElement &elem)
Creates the instance from XML by calling readXml of derived classes.
static bool equals(const QgsMesh3DAveragingMethod *a, const QgsMesh3DAveragingMethod *b)
Returns whether two methods equal.
QgsMeshDataBlock calculate(const QgsMesh3DDataBlock &block3d, QgsFeedback *feedback=nullptr) const
Calculated 2d block values from 3d stacked mesh values.
Method method() const
Returns type of averaging method.
Method
Type of averaging method.
@ RelativeHeightAveragingMethod
Method to average values defined by range of relative length units to the surface or bed level.
@ MultiLevelsAveragingMethod
Method to average values from selected vertical layers.
@ ElevationAveragingMethod
Method to average values defined by range of absolute length units to the model's datum.
@ SigmaAveragingMethod
Method to average values between 0 (bed level) and 1 (surface).
QgsMesh3DAveragingMethod(Method method)
Ctor.
A block of 3d stacked mesh data related N faces defined on base mesh frame.
QVector< double > values() const
Returns the values at volume centers.
bool isVector() const
Whether we store vector values.
int count() const
Number of 2d faces for which the volume data is stored in the block.
QVector< int > verticalLevelsCount() const
Returns number of vertical level above 2d faces.
bool isValid() const
Whether the block is valid.
QVector< double > verticalLevels() const
Returns the vertical levels height.
A block of integers/doubles from a mesh dataset.
@ ScalarDouble
Scalar double values.
@ Vector2DDouble
Vector double pairs (x1, y1, x2, y2, ... ).
void setValues(const QVector< double > &vals)
Sets values.
double startElevation() const
Returns start elevation.
void readXml(const QDomElement &elem) override
Reads configuration from the given DOM element.
double endElevation() const
Returns end elevation.
QgsMesh3DAveragingMethod * clone() const override
Clone the instance.
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
bool equals(const QgsMesh3DAveragingMethod *other) const override
Returns whether method equals to other.
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
QgsMeshMultiLevelsAveragingMethod()
Constructs single level averaging method for 1st (top) vertical level.
int endVerticalLevel() const
Returns ending vertical level.
bool equals(const QgsMesh3DAveragingMethod *other) const override
Returns whether method equals to other.
QgsMesh3DAveragingMethod * clone() const override
Clone the instance.
bool countedFromTop() const
Returns whether the start and end vertical levels are indexed from top (surface) or bottom (bed) leve...
bool isSingleLevel() const
Returns whether the averaging method selects only a single vertical level.
void readXml(const QDomElement &elem) override
Reads configuration from the given DOM element.
int startVerticalLevel() const
Returns starting vertical level.
void readXml(const QDomElement &elem) override
Reads configuration from the given DOM element.
double startHeight() const
Returns starting depth/height.
QgsMeshRelativeHeightAveragingMethod()
Constructs default depth averaging method.
bool countedFromTop() const
Returns whether the start and end vertical levels are relative to top (surface) or bottom (bed) level...
bool equals(const QgsMesh3DAveragingMethod *other) const override
Returns whether method equals to other.
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
double endHeight() const
Returns ending depth/height.
QgsMesh3DAveragingMethod * clone() const override
Clone the instance.
double endFraction() const
Returns ending fraction.
QgsMeshSigmaAveragingMethod()
Constructs the sigma method for whole value range 0-1.
void readXml(const QDomElement &elem) override
Reads configuration from the given DOM element.
bool equals(const QgsMesh3DAveragingMethod *other) const override
Returns whether method equals to other.
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
QgsMesh3DAveragingMethod * clone() const override
Clone the instance.
~QgsMeshSigmaAveragingMethod() override
double startFraction() const
Returns starting fraction.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607