QGIS API Documentation 3.99.0-Master (d270888f95f)
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#include <QString>
27
28using namespace Qt::StringLiterals;
29
30// threshold for length intervals, to avoid division by 0
31static const double eps = 1e-6;
32
37
39{
40 std::unique_ptr<QgsMesh3DAveragingMethod> ret;
41
43 elem.attribute( u"method"_s ).toInt() );
44 switch ( method )
45 {
47 ret = std::make_unique<QgsMeshMultiLevelsAveragingMethod>( );
48 break;
50 ret = std::make_unique<QgsMeshSigmaAveragingMethod>( );
51 break;
53 ret = std::make_unique<QgsMeshRelativeHeightAveragingMethod>( );
54 break;
56 ret = std::make_unique<QgsMeshElevationAveragingMethod>( );
57 break;
58 }
59 ret->readXml( elem );
60 return ret.release();
61}
62
64{
65 if ( !block3d.isValid() )
66 return QgsMeshDataBlock();
67
68 if ( !hasValidInputs() )
69 return QgsMeshDataBlock();
70
71 const bool isVector = block3d.isVector();
72 const int count = block3d.count();
74 QVector<double> valuesFaces( isVector ? 2 * count : count, std::numeric_limits<double>::quiet_NaN() );
75 const QVector<int> verticalLevelsCount = block3d.verticalLevelsCount();
76 const QVector<double> verticalLevels = block3d.verticalLevels();
77 const QVector<double> volumeValues = block3d.values();
78
79 int startVolumeIndex = 0;
80 for ( int faceIndex = 0; faceIndex < count; ++faceIndex )
81 {
82 if ( feedback && feedback->isCanceled() )
83 {
84 return QgsMeshDataBlock();
85 }
86
87 const int volumesBelowFaceCount = verticalLevelsCount[faceIndex];
88 if ( volumesBelowFaceCount <= 0 )
89 continue;
90
91 const int startVerticalLevelIndex = startVolumeIndex + faceIndex;
92 Q_ASSERT( verticalLevels.size() >= startVerticalLevelIndex + volumesBelowFaceCount + 1 );
93 QVector<double> verticalLevelsForFace = verticalLevels.mid( startVerticalLevelIndex, volumesBelowFaceCount + 1 );
94 double faceLevelTop = verticalLevelsForFace[0];
95 double faceLevelBottom = verticalLevelsForFace[verticalLevelsForFace.size() - 1];
96
97 // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
98 if ( faceLevelTop < faceLevelBottom )
99 {
100 std::swap( faceLevelTop, faceLevelBottom );
101 }
102
103 double methodLevelTop = std::numeric_limits<double>::quiet_NaN();
104 double methodLevelBottom = std::numeric_limits<double>::quiet_NaN();
105
106 int singleVerticalIndex = -1;
107 volumeRangeForFace( methodLevelTop,
108 methodLevelBottom,
109 singleVerticalIndex,
110 verticalLevelsForFace );
111
112 if ( singleVerticalIndex != -1 )
113 {
114 int volumeIndex = singleVerticalIndex + startVolumeIndex;
115 if ( isVector )
116 {
117 valuesFaces[2 * faceIndex] = volumeValues.at( 2 * volumeIndex );
118 valuesFaces[2 * faceIndex + 1 ] = volumeValues.at( 2 * volumeIndex + 1 );
119 }
120 else
121 {
122 valuesFaces[faceIndex] = volumeValues.at( volumeIndex );
123 }
124 }
125 else if ( !std::isnan( methodLevelTop ) && !std::isnan( methodLevelBottom ) )
126 {
127 // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
128 if ( methodLevelTop < methodLevelBottom )
129 {
130 std::swap( methodLevelTop, methodLevelBottom );
131 }
132
133 // check if we are completely outside the limits
134 if ( ( methodLevelTop >= faceLevelBottom ) && ( methodLevelBottom <= faceLevelTop ) )
135 {
136 averageVolumeValuesForFace(
137 faceIndex,
138 volumesBelowFaceCount,
139 startVolumeIndex,
140 methodLevelTop,
141 methodLevelBottom,
142 isVector,
143 verticalLevelsForFace,
144 volumeValues,
145 valuesFaces
146 );
147 }
148 }
149
150 // move to next face and associated volumes
151 startVolumeIndex += volumesBelowFaceCount;
152 }
153 result.setValues( valuesFaces );
154 return result;
155}
156
161
162void QgsMesh3DAveragingMethod::averageVolumeValuesForFace(
163 int faceIndex,
164 int volumesBelowFaceCount,
165 int startVolumeIndex,
166 double methodLevelTop,
167 double methodLevelBottom,
168 bool isVector,
169 const QVector<double> &verticalLevelsForFace,
170 const QVector<double> &volumeValues,
171 QVector<double> &valuesFaces
172) const
173{
174 double totalAveragedHeight = 0;
175 double nSumX = 0.0;
176 double nSumY = 0.0;
177
178 // Now go through all volumes below face and check if we need to take that volume into consideration
179 for ( int relativeVolumeIndex = 0; relativeVolumeIndex < volumesBelowFaceCount; ++relativeVolumeIndex )
180 {
181 const int volumeIndex = startVolumeIndex + relativeVolumeIndex;
182 double volumeLevelTop = verticalLevelsForFace[relativeVolumeIndex];
183 double volumeLevelBottom = verticalLevelsForFace[relativeVolumeIndex + 1];
184 if ( volumeLevelTop < volumeLevelBottom )
185 {
186 std::swap( volumeLevelTop, volumeLevelBottom );
187 }
188
189 const double intersectionLevelTop = std::min( methodLevelTop, volumeLevelTop );
190 const double intersectionLevelBottom = std::max( methodLevelBottom, volumeLevelBottom );
191 const double effectiveInterval = intersectionLevelTop - intersectionLevelBottom;
192
193 if ( effectiveInterval > eps )
194 {
195 if ( isVector )
196 {
197 const double x = volumeValues[2 * volumeIndex ];
198 const double y = volumeValues[ 2 * volumeIndex + 1 ];
199 if ( ! std::isnan( x ) &&
200 ! std::isnan( y )
201 )
202 {
203 nSumX += x * effectiveInterval;
204 nSumY += y * effectiveInterval;
205 totalAveragedHeight += effectiveInterval;
206 }
207 }
208 else
209 {
210 const double x = volumeValues[ volumeIndex ];
211 if ( ! std::isnan( x ) )
212 {
213 nSumX += x * effectiveInterval;
214 totalAveragedHeight += effectiveInterval;
215 }
216 }
217 }
218 }
219
220 // calculate average
221 if ( totalAveragedHeight > eps )
222 {
223 if ( isVector )
224 {
225 valuesFaces[2 * faceIndex] = nSumX / totalAveragedHeight;
226 valuesFaces[2 * faceIndex + 1 ] = nSumY / totalAveragedHeight;
227 }
228 else
229 {
230 valuesFaces[faceIndex] = nSumX / totalAveragedHeight;
231 }
232 }
233}
234
236{
237 if ( a )
238 return a->equals( b );
239 else
240 return !b;
241}
242
245 , mStartVerticalLevel( startLevel )
246 , mEndVerticalLevel( endLevel )
247 , mCountedFromTop( countedFromTop )
248{
249 if ( mStartVerticalLevel > mEndVerticalLevel )
250 {
251 std::swap( mStartVerticalLevel, mEndVerticalLevel );
252 }
253}
254
259
262 , mStartVerticalLevel( verticalLevel )
263 , mEndVerticalLevel( verticalLevel )
264 , mCountedFromTop( countedFromTop )
265{
266}
267
269
270QDomElement QgsMeshMultiLevelsAveragingMethod::writeXml( QDomDocument &doc ) const
271{
272 QDomElement elem = doc.createElement( u"multi-vertical-layers-settings"_s );
273 elem.setAttribute( u"start-layer-index"_s, startVerticalLevel() );
274 elem.setAttribute( u"end-layer-index"_s, endVerticalLevel() );
275 return elem;
276}
277
278void QgsMeshMultiLevelsAveragingMethod::readXml( const QDomElement &elem )
279{
280 const QDomElement settings = elem.firstChildElement( u"multi-vertical-layers-settings"_s );
281 if ( !settings.isNull() )
282 {
283 mStartVerticalLevel = settings.attribute( u"start-layer-index"_s ).toInt();
284 mEndVerticalLevel = settings.attribute( u"end-layer-index"_s ).toInt();
285 if ( mStartVerticalLevel > mEndVerticalLevel )
286 {
287 std::swap( mStartVerticalLevel, mEndVerticalLevel );
288 }
289 }
290}
291
293{
294 if ( !other || other->method() != method() )
295 return false;
296
297 const QgsMeshMultiLevelsAveragingMethod *otherMethod = static_cast<const QgsMeshMultiLevelsAveragingMethod *>( other );
298
299 return ( otherMethod->startVerticalLevel() == startVerticalLevel() ) &&
300 ( otherMethod->endVerticalLevel() == endVerticalLevel() ) &&
301 ( otherMethod->countedFromTop() == countedFromTop() );
302}
303
308
309
311{
312 return mStartVerticalLevel;
313}
314
316{
317 return mEndVerticalLevel;
318}
319
320bool QgsMeshMultiLevelsAveragingMethod::hasValidInputs() const
321{
322 return mStartVerticalLevel >= 1 && mEndVerticalLevel >= mStartVerticalLevel;
323}
324
325void QgsMeshMultiLevelsAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
326 double &endVerticalLevel,
327 int &singleVerticalIndex,
328 const QVector<double> &verticalLevels ) const
329{
330 Q_ASSERT( mStartVerticalLevel <= mEndVerticalLevel );
331
332 if ( countedFromTop() )
333 {
334 const int startIndex = mStartVerticalLevel - 1;
335 if ( mStartVerticalLevel == mEndVerticalLevel )
336 {
337 if ( startIndex >= 0 && startIndex < verticalLevels.size() - 1 )
338 singleVerticalIndex = startIndex;
339 }
340 else
341 {
342 if ( startIndex >= 0 && startIndex < verticalLevels.size() )
343 {
344 startVerticalLevel = verticalLevels[ startIndex ];
345 }
346
347 if ( mEndVerticalLevel >= 0 && mEndVerticalLevel < verticalLevels.size() )
348 {
349 endVerticalLevel = verticalLevels[ mEndVerticalLevel ];
350 }
351 else
352 {
353 endVerticalLevel = verticalLevels[ verticalLevels.size() - 1 ];
354 }
355 }
356 }
357 else
358 {
359 const int volumesBelowFaceCount = verticalLevels.size() - 1;
360 const int startIndex = volumesBelowFaceCount - mEndVerticalLevel;
361 if ( mStartVerticalLevel == mEndVerticalLevel )
362 {
363 if ( startIndex >= 0 && startIndex < verticalLevels.size() - 1 )
364 singleVerticalIndex = startIndex;
365 }
366 else
367 {
368 if ( startIndex >= 0 && startIndex < verticalLevels.size() )
369 {
370 startVerticalLevel = verticalLevels[ startIndex ];
371 }
372 else
373 {
374 startVerticalLevel = verticalLevels[ 0 ];
375 }
376
377 const int endIndex = volumesBelowFaceCount - mStartVerticalLevel + 1;
378 if ( endIndex >= 0 && endIndex < verticalLevels.size() )
379 {
380 endVerticalLevel = verticalLevels[ endIndex ];
381 }
382 }
383 }
384}
385
390
393 , mStartFraction( startFraction )
394 , mEndFraction( endFraction )
395{
396 if ( mStartFraction > mEndFraction )
397 {
398 std::swap( mStartFraction, mEndFraction );
399 }
400}
401
403
404QDomElement QgsMeshSigmaAveragingMethod::writeXml( QDomDocument &doc ) const
405{
406 QDomElement elem = doc.createElement( u"sigma-settings"_s );
407 elem.setAttribute( u"start-fraction"_s, startFraction() );
408 elem.setAttribute( u"end-fraction"_s, endFraction() );
409 return elem;
410}
411
412void QgsMeshSigmaAveragingMethod::readXml( const QDomElement &elem )
413{
414 const QDomElement settings = elem.firstChildElement( u"sigma-settings"_s );
415 if ( !settings.isNull() )
416 {
417 mStartFraction = settings.attribute( u"start-fraction"_s ).toDouble();
418 mEndFraction = settings.attribute( u"end-fraction"_s ).toDouble();
419 if ( mStartFraction > mEndFraction )
420 {
421 std::swap( mStartFraction, mEndFraction );
422 }
423 }
424}
425
427{
428 if ( !other || other->method() != method() )
429 return false;
430
431 const QgsMeshSigmaAveragingMethod *otherMethod = static_cast<const QgsMeshSigmaAveragingMethod *>( other );
432
433 return qgsDoubleNear( otherMethod->startFraction(), startFraction() ) && qgsDoubleNear( otherMethod->endFraction(), endFraction() ) ;
434}
435
440
442{
443 return mStartFraction;
444}
445
447{
448 return mEndFraction;
449}
450
451bool QgsMeshSigmaAveragingMethod::hasValidInputs() const
452{
453 return mStartFraction >= 0 && mEndFraction >= mStartFraction && mEndFraction <= 1;
454}
455
456void QgsMeshSigmaAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
457 double &endVerticalLevel,
458 int &,
459 const QVector<double> &verticalLevels ) const
460{
461 const double top = verticalLevels[ 0 ];
462 const double bot = verticalLevels[ verticalLevels.size() - 1 ];
463 const double diff = top - bot;
464
465 if ( mStartFraction < 0 )
466 startVerticalLevel = bot;
467 else
468 startVerticalLevel = bot + diff * mStartFraction;
469
470 if ( mEndFraction > 1 )
471 endVerticalLevel = top;
472 else
473 endVerticalLevel = bot + diff * mEndFraction;
474}
475
477{
478 return mCountedFromTop;
479}
480
482{
483 return mStartVerticalLevel == mEndVerticalLevel;
484}
485
486
491
494 , mStartHeight( startDepth )
495 , mEndHeight( endDepth )
496 , mCountedFromTop( countedFromTop )
497{
498 if ( mStartHeight > mEndHeight )
499 {
500 std::swap( mStartHeight, mEndHeight );
501 }
502}
503
505
506QDomElement QgsMeshRelativeHeightAveragingMethod::writeXml( QDomDocument &doc ) const
507{
508 QDomElement elem = doc.createElement( u"relative-height-settings"_s );
509 elem.setAttribute( u"start-height"_s, startHeight() );
510 elem.setAttribute( u"end-height"_s, endHeight() );
511 return elem;
512}
513
515{
516 const QDomElement settings = elem.firstChildElement( u"relative-height-settings"_s );
517 if ( !settings.isNull() )
518 {
519 mStartHeight = settings.attribute( u"start-height"_s ).toDouble();
520 mEndHeight = settings.attribute( u"end-height"_s ).toDouble();
521 if ( mStartHeight > mEndHeight )
522 {
523 std::swap( mStartHeight, mEndHeight );
524 }
525 }
526}
527
529{
530 if ( !other || other->method() != method() )
531 return false;
532
533 const QgsMeshRelativeHeightAveragingMethod *otherMethod = static_cast<const QgsMeshRelativeHeightAveragingMethod *>( other );
534
535 return qgsDoubleNear( otherMethod->startHeight(), startHeight() ) &&
536 qgsDoubleNear( otherMethod->endHeight(), endHeight() ) &&
537 otherMethod->countedFromTop() == countedFromTop();
538}
539
544
546{
547 return mStartHeight;
548}
549
551{
552 return mEndHeight;
553}
554
555bool QgsMeshRelativeHeightAveragingMethod::hasValidInputs() const
556{
557 return mStartHeight >= 0 && mEndHeight >= mStartHeight;
558}
559
560void QgsMeshRelativeHeightAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
561 double &endVerticalLevel,
562 int &,
563 const QVector<double> &verticalLevels ) const
564{
565 if ( countedFromTop() )
566 {
567 const double top = verticalLevels[ 0 ];
568 startVerticalLevel = top - mStartHeight;
569 endVerticalLevel = top - mEndHeight;
570 }
571 else
572 {
573 const double bot = verticalLevels[verticalLevels.size() - 1];
574 startVerticalLevel = bot + mStartHeight;
575 endVerticalLevel = bot + mEndHeight;
576 }
577}
578
580{
581 return mCountedFromTop;
582}
583
588
591 , mStartElevation( startElevation )
592 , mEndElevation( endElevation )
593{
594 if ( mEndElevation > mStartElevation )
595 {
596 std::swap( mEndElevation, mStartElevation );
597 }
598}
599
601
602QDomElement QgsMeshElevationAveragingMethod::writeXml( QDomDocument &doc ) const
603{
604 QDomElement elem = doc.createElement( u"elevation-settings"_s );
605 elem.setAttribute( u"start-elevation"_s, startElevation() );
606 elem.setAttribute( u"end-elevation"_s, endElevation() );
607 return elem;
608}
609
610void QgsMeshElevationAveragingMethod::readXml( const QDomElement &elem )
611{
612 const QDomElement settings = elem.firstChildElement( u"elevation-settings"_s );
613 if ( !settings.isNull() )
614 {
615 mStartElevation = settings.attribute( u"start-elevation"_s ).toDouble();
616 mEndElevation = settings.attribute( u"end-elevation"_s ).toDouble();
617 if ( mEndElevation > mStartElevation )
618 {
619 std::swap( mEndElevation, mStartElevation );
620 }
621 }
622}
623
625{
626 if ( !other || other->method() != method() )
627 return false;
628
629 const QgsMeshElevationAveragingMethod *otherMethod = static_cast<const QgsMeshElevationAveragingMethod *>( other );
630
631 return qgsDoubleNear( otherMethod->startElevation(), startElevation() ) && qgsDoubleNear( otherMethod->endElevation(), endElevation() ) ;
632}
633
638
640{
641 return mStartElevation;
642}
643
645{
646 return mEndElevation;
647}
648
649bool QgsMeshElevationAveragingMethod::hasValidInputs() const
650{
651 return mStartElevation <= 0.0 && mEndElevation <= mStartElevation;
652}
653
654void QgsMeshElevationAveragingMethod::volumeRangeForFace( double &startVerticalLevel,
655 double &endVerticalLevel,
656 int &,
657 const QVector<double> &verticalLevels ) const
658{
659 Q_UNUSED( verticalLevels )
660 startVerticalLevel = mStartElevation;
661 endVerticalLevel = mEndElevation;
662}
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:55
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:6900