QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
36
38{
39 std::unique_ptr<QgsMesh3DAveragingMethod> ret;
40
41 const QgsMesh3DAveragingMethod::Method method = static_cast<QgsMesh3DAveragingMethod::Method>( elem.attribute( u"method"_s ).toInt() );
42 switch ( method )
43 {
45 ret = std::make_unique<QgsMeshMultiLevelsAveragingMethod>();
46 break;
48 ret = std::make_unique<QgsMeshSigmaAveragingMethod>();
49 break;
51 ret = std::make_unique<QgsMeshRelativeHeightAveragingMethod>();
52 break;
54 ret = std::make_unique<QgsMeshElevationAveragingMethod>();
55 break;
56 }
57 ret->readXml( elem );
58 return ret.release();
59}
60
62{
63 if ( !block3d.isValid() )
64 return QgsMeshDataBlock();
65
66 if ( !hasValidInputs() )
67 return QgsMeshDataBlock();
68
69 const bool isVector = block3d.isVector();
70 const int count = block3d.count();
72 QVector<double> valuesFaces( isVector ? 2 * count : count, std::numeric_limits<double>::quiet_NaN() );
73 const QVector<int> verticalLevelsCount = block3d.verticalLevelsCount();
74 const QVector<double> verticalLevels = block3d.verticalLevels();
75 const QVector<double> volumeValues = block3d.values();
76
77 int startVolumeIndex = 0;
78 for ( int faceIndex = 0; faceIndex < count; ++faceIndex )
79 {
80 if ( feedback && feedback->isCanceled() )
81 {
82 return QgsMeshDataBlock();
83 }
84
85 const int volumesBelowFaceCount = verticalLevelsCount[faceIndex];
86 if ( volumesBelowFaceCount <= 0 )
87 continue;
88
89 const int startVerticalLevelIndex = startVolumeIndex + faceIndex;
90 Q_ASSERT( verticalLevels.size() >= startVerticalLevelIndex + volumesBelowFaceCount + 1 );
91 QVector<double> verticalLevelsForFace = verticalLevels.mid( startVerticalLevelIndex, volumesBelowFaceCount + 1 );
92 double faceLevelTop = verticalLevelsForFace[0];
93 double faceLevelBottom = verticalLevelsForFace[verticalLevelsForFace.size() - 1];
94
95 // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
96 if ( faceLevelTop < faceLevelBottom )
97 {
98 std::swap( faceLevelTop, faceLevelBottom );
99 }
100
101 double methodLevelTop = std::numeric_limits<double>::quiet_NaN();
102 double methodLevelBottom = std::numeric_limits<double>::quiet_NaN();
103
104 int singleVerticalIndex = -1;
105 volumeRangeForFace( methodLevelTop, methodLevelBottom, singleVerticalIndex, verticalLevelsForFace );
106
107 if ( singleVerticalIndex != -1 )
108 {
109 int volumeIndex = singleVerticalIndex + startVolumeIndex;
110 if ( isVector )
111 {
112 valuesFaces[2 * faceIndex] = volumeValues.at( 2 * volumeIndex );
113 valuesFaces[2 * faceIndex + 1] = volumeValues.at( 2 * volumeIndex + 1 );
114 }
115 else
116 {
117 valuesFaces[faceIndex] = volumeValues.at( volumeIndex );
118 }
119 }
120 else if ( !std::isnan( methodLevelTop ) && !std::isnan( methodLevelBottom ) )
121 {
122 // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
123 if ( methodLevelTop < methodLevelBottom )
124 {
125 std::swap( methodLevelTop, methodLevelBottom );
126 }
127
128 // check if we are completely outside the limits
129 if ( ( methodLevelTop >= faceLevelBottom ) && ( methodLevelBottom <= faceLevelTop ) )
130 {
131 averageVolumeValuesForFace( faceIndex, volumesBelowFaceCount, startVolumeIndex, methodLevelTop, methodLevelBottom, isVector, verticalLevelsForFace, volumeValues, valuesFaces );
132 }
133 }
134
135 // move to next face and associated volumes
136 startVolumeIndex += volumesBelowFaceCount;
137 }
138 result.setValues( valuesFaces );
139 return result;
140}
141
146
147void QgsMesh3DAveragingMethod::averageVolumeValuesForFace(
148 int faceIndex,
149 int volumesBelowFaceCount,
150 int startVolumeIndex,
151 double methodLevelTop,
152 double methodLevelBottom,
153 bool isVector,
154 const QVector<double> &verticalLevelsForFace,
155 const QVector<double> &volumeValues,
156 QVector<double> &valuesFaces
157) const
158{
159 double totalAveragedHeight = 0;
160 double nSumX = 0.0;
161 double nSumY = 0.0;
162
163 // Now go through all volumes below face and check if we need to take that volume into consideration
164 for ( int relativeVolumeIndex = 0; relativeVolumeIndex < volumesBelowFaceCount; ++relativeVolumeIndex )
165 {
166 const int volumeIndex = startVolumeIndex + relativeVolumeIndex;
167 double volumeLevelTop = verticalLevelsForFace[relativeVolumeIndex];
168 double volumeLevelBottom = verticalLevelsForFace[relativeVolumeIndex + 1];
169 if ( volumeLevelTop < volumeLevelBottom )
170 {
171 std::swap( volumeLevelTop, volumeLevelBottom );
172 }
173
174 const double intersectionLevelTop = std::min( methodLevelTop, volumeLevelTop );
175 const double intersectionLevelBottom = std::max( methodLevelBottom, volumeLevelBottom );
176 const double effectiveInterval = intersectionLevelTop - intersectionLevelBottom;
177
178 if ( effectiveInterval > eps )
179 {
180 if ( isVector )
181 {
182 const double x = volumeValues[2 * volumeIndex];
183 const double y = volumeValues[2 * volumeIndex + 1];
184 if ( !std::isnan( x ) && !std::isnan( y ) )
185 {
186 nSumX += x * effectiveInterval;
187 nSumY += y * effectiveInterval;
188 totalAveragedHeight += effectiveInterval;
189 }
190 }
191 else
192 {
193 const double x = volumeValues[volumeIndex];
194 if ( !std::isnan( x ) )
195 {
196 nSumX += x * effectiveInterval;
197 totalAveragedHeight += effectiveInterval;
198 }
199 }
200 }
201 }
202
203 // calculate average
204 if ( totalAveragedHeight > eps )
205 {
206 if ( isVector )
207 {
208 valuesFaces[2 * faceIndex] = nSumX / totalAveragedHeight;
209 valuesFaces[2 * faceIndex + 1] = nSumY / totalAveragedHeight;
210 }
211 else
212 {
213 valuesFaces[faceIndex] = nSumX / totalAveragedHeight;
214 }
215 }
216}
217
219{
220 if ( a )
221 return a->equals( b );
222 else
223 return !b;
224}
225
228 , mStartVerticalLevel( startLevel )
229 , mEndVerticalLevel( endLevel )
230 , mCountedFromTop( countedFromTop )
231{
232 if ( mStartVerticalLevel > mEndVerticalLevel )
233 {
234 std::swap( mStartVerticalLevel, mEndVerticalLevel );
235 }
236}
237
241
244 , mStartVerticalLevel( verticalLevel )
245 , mEndVerticalLevel( verticalLevel )
246 , mCountedFromTop( countedFromTop )
247{}
248
250
251QDomElement QgsMeshMultiLevelsAveragingMethod::writeXml( QDomDocument &doc ) const
252{
253 QDomElement elem = doc.createElement( u"multi-vertical-layers-settings"_s );
254 elem.setAttribute( u"start-layer-index"_s, startVerticalLevel() );
255 elem.setAttribute( u"end-layer-index"_s, endVerticalLevel() );
256 return elem;
257}
258
259void QgsMeshMultiLevelsAveragingMethod::readXml( const QDomElement &elem )
260{
261 const QDomElement settings = elem.firstChildElement( u"multi-vertical-layers-settings"_s );
262 if ( !settings.isNull() )
263 {
264 mStartVerticalLevel = settings.attribute( u"start-layer-index"_s ).toInt();
265 mEndVerticalLevel = settings.attribute( u"end-layer-index"_s ).toInt();
266 if ( mStartVerticalLevel > mEndVerticalLevel )
267 {
268 std::swap( mStartVerticalLevel, mEndVerticalLevel );
269 }
270 }
271}
272
274{
275 if ( !other || other->method() != method() )
276 return false;
277
278 const QgsMeshMultiLevelsAveragingMethod *otherMethod = static_cast<const QgsMeshMultiLevelsAveragingMethod *>( other );
279
280 return ( otherMethod->startVerticalLevel() == startVerticalLevel() ) && ( otherMethod->endVerticalLevel() == endVerticalLevel() ) && ( otherMethod->countedFromTop() == countedFromTop() );
281}
282
287
288
290{
291 return mStartVerticalLevel;
292}
293
295{
296 return mEndVerticalLevel;
297}
298
299bool QgsMeshMultiLevelsAveragingMethod::hasValidInputs() const
300{
301 return mStartVerticalLevel >= 1 && mEndVerticalLevel >= mStartVerticalLevel;
302}
303
304void QgsMeshMultiLevelsAveragingMethod::volumeRangeForFace( double &startVerticalLevel, double &endVerticalLevel, int &singleVerticalIndex, const QVector<double> &verticalLevels ) const
305{
306 Q_ASSERT( mStartVerticalLevel <= mEndVerticalLevel );
307
308 if ( countedFromTop() )
309 {
310 const int startIndex = mStartVerticalLevel - 1;
311 if ( mStartVerticalLevel == mEndVerticalLevel )
312 {
313 if ( startIndex >= 0 && startIndex < verticalLevels.size() - 1 )
314 singleVerticalIndex = startIndex;
315 }
316 else
317 {
318 if ( startIndex >= 0 && startIndex < verticalLevels.size() )
319 {
320 startVerticalLevel = verticalLevels[startIndex];
321 }
322
323 if ( mEndVerticalLevel >= 0 && mEndVerticalLevel < verticalLevels.size() )
324 {
325 endVerticalLevel = verticalLevels[mEndVerticalLevel];
326 }
327 else
328 {
329 endVerticalLevel = verticalLevels[verticalLevels.size() - 1];
330 }
331 }
332 }
333 else
334 {
335 const int volumesBelowFaceCount = verticalLevels.size() - 1;
336 const int startIndex = volumesBelowFaceCount - mEndVerticalLevel;
337 if ( mStartVerticalLevel == mEndVerticalLevel )
338 {
339 if ( startIndex >= 0 && startIndex < verticalLevels.size() - 1 )
340 singleVerticalIndex = startIndex;
341 }
342 else
343 {
344 if ( startIndex >= 0 && startIndex < verticalLevels.size() )
345 {
346 startVerticalLevel = verticalLevels[startIndex];
347 }
348 else
349 {
350 startVerticalLevel = verticalLevels[0];
351 }
352
353 const int endIndex = volumesBelowFaceCount - mStartVerticalLevel + 1;
354 if ( endIndex >= 0 && endIndex < verticalLevels.size() )
355 {
356 endVerticalLevel = verticalLevels[endIndex];
357 }
358 }
359 }
360}
361
365
368 , mStartFraction( startFraction )
369 , mEndFraction( endFraction )
370{
371 if ( mStartFraction > mEndFraction )
372 {
373 std::swap( mStartFraction, mEndFraction );
374 }
375}
376
378
379QDomElement QgsMeshSigmaAveragingMethod::writeXml( QDomDocument &doc ) const
380{
381 QDomElement elem = doc.createElement( u"sigma-settings"_s );
382 elem.setAttribute( u"start-fraction"_s, startFraction() );
383 elem.setAttribute( u"end-fraction"_s, endFraction() );
384 return elem;
385}
386
387void QgsMeshSigmaAveragingMethod::readXml( const QDomElement &elem )
388{
389 const QDomElement settings = elem.firstChildElement( u"sigma-settings"_s );
390 if ( !settings.isNull() )
391 {
392 mStartFraction = settings.attribute( u"start-fraction"_s ).toDouble();
393 mEndFraction = settings.attribute( u"end-fraction"_s ).toDouble();
394 if ( mStartFraction > mEndFraction )
395 {
396 std::swap( mStartFraction, mEndFraction );
397 }
398 }
399}
400
402{
403 if ( !other || other->method() != method() )
404 return false;
405
406 const QgsMeshSigmaAveragingMethod *otherMethod = static_cast<const QgsMeshSigmaAveragingMethod *>( other );
407
408 return qgsDoubleNear( otherMethod->startFraction(), startFraction() ) && qgsDoubleNear( otherMethod->endFraction(), endFraction() );
409}
410
415
417{
418 return mStartFraction;
419}
420
422{
423 return mEndFraction;
424}
425
426bool QgsMeshSigmaAveragingMethod::hasValidInputs() const
427{
428 return mStartFraction >= 0 && mEndFraction >= mStartFraction && mEndFraction <= 1;
429}
430
431void QgsMeshSigmaAveragingMethod::volumeRangeForFace( double &startVerticalLevel, double &endVerticalLevel, int &, const QVector<double> &verticalLevels ) const
432{
433 const double top = verticalLevels[0];
434 const double bot = verticalLevels[verticalLevels.size() - 1];
435 const double diff = top - bot;
436
437 if ( mStartFraction < 0 )
438 startVerticalLevel = bot;
439 else
440 startVerticalLevel = bot + diff * mStartFraction;
441
442 if ( mEndFraction > 1 )
443 endVerticalLevel = top;
444 else
445 endVerticalLevel = bot + diff * mEndFraction;
446}
447
449{
450 return mCountedFromTop;
451}
452
454{
455 return mStartVerticalLevel == mEndVerticalLevel;
456}
457
458
462
465 , mStartHeight( startDepth )
466 , mEndHeight( endDepth )
467 , mCountedFromTop( countedFromTop )
468{
469 if ( mStartHeight > mEndHeight )
470 {
471 std::swap( mStartHeight, mEndHeight );
472 }
473}
474
476
477QDomElement QgsMeshRelativeHeightAveragingMethod::writeXml( QDomDocument &doc ) const
478{
479 QDomElement elem = doc.createElement( u"relative-height-settings"_s );
480 elem.setAttribute( u"start-height"_s, startHeight() );
481 elem.setAttribute( u"end-height"_s, endHeight() );
482 return elem;
483}
484
486{
487 const QDomElement settings = elem.firstChildElement( u"relative-height-settings"_s );
488 if ( !settings.isNull() )
489 {
490 mStartHeight = settings.attribute( u"start-height"_s ).toDouble();
491 mEndHeight = settings.attribute( u"end-height"_s ).toDouble();
492 if ( mStartHeight > mEndHeight )
493 {
494 std::swap( mStartHeight, mEndHeight );
495 }
496 }
497}
498
500{
501 if ( !other || other->method() != method() )
502 return false;
503
504 const QgsMeshRelativeHeightAveragingMethod *otherMethod = static_cast<const QgsMeshRelativeHeightAveragingMethod *>( other );
505
506 return qgsDoubleNear( otherMethod->startHeight(), startHeight() ) && qgsDoubleNear( otherMethod->endHeight(), endHeight() ) && otherMethod->countedFromTop() == countedFromTop();
507}
508
513
515{
516 return mStartHeight;
517}
518
520{
521 return mEndHeight;
522}
523
524bool QgsMeshRelativeHeightAveragingMethod::hasValidInputs() const
525{
526 return mStartHeight >= 0 && mEndHeight >= mStartHeight;
527}
528
529void QgsMeshRelativeHeightAveragingMethod::volumeRangeForFace( double &startVerticalLevel, double &endVerticalLevel, int &, const QVector<double> &verticalLevels ) const
530{
531 if ( countedFromTop() )
532 {
533 const double top = verticalLevels[0];
534 startVerticalLevel = top - mStartHeight;
535 endVerticalLevel = top - mEndHeight;
536 }
537 else
538 {
539 const double bot = verticalLevels[verticalLevels.size() - 1];
540 startVerticalLevel = bot + mStartHeight;
541 endVerticalLevel = bot + mEndHeight;
542 }
543}
544
546{
547 return mCountedFromTop;
548}
549
553
556 , mStartElevation( startElevation )
557 , mEndElevation( endElevation )
558{
559 if ( mEndElevation > mStartElevation )
560 {
561 std::swap( mEndElevation, mStartElevation );
562 }
563}
564
566
567QDomElement QgsMeshElevationAveragingMethod::writeXml( QDomDocument &doc ) const
568{
569 QDomElement elem = doc.createElement( u"elevation-settings"_s );
570 elem.setAttribute( u"start-elevation"_s, startElevation() );
571 elem.setAttribute( u"end-elevation"_s, endElevation() );
572 return elem;
573}
574
575void QgsMeshElevationAveragingMethod::readXml( const QDomElement &elem )
576{
577 const QDomElement settings = elem.firstChildElement( u"elevation-settings"_s );
578 if ( !settings.isNull() )
579 {
580 mStartElevation = settings.attribute( u"start-elevation"_s ).toDouble();
581 mEndElevation = settings.attribute( u"end-elevation"_s ).toDouble();
582 if ( mEndElevation > mStartElevation )
583 {
584 std::swap( mEndElevation, mStartElevation );
585 }
586 }
587}
588
590{
591 if ( !other || other->method() != method() )
592 return false;
593
594 const QgsMeshElevationAveragingMethod *otherMethod = static_cast<const QgsMeshElevationAveragingMethod *>( other );
595
596 return qgsDoubleNear( otherMethod->startElevation(), startElevation() ) && qgsDoubleNear( otherMethod->endElevation(), endElevation() );
597}
598
603
605{
606 return mStartElevation;
607}
608
610{
611 return mEndElevation;
612}
613
614bool QgsMeshElevationAveragingMethod::hasValidInputs() const
615{
616 return mStartElevation <= 0.0 && mEndElevation <= mStartElevation;
617}
618
619void QgsMeshElevationAveragingMethod::volumeRangeForFace( double &startVerticalLevel, double &endVerticalLevel, int &, const QVector<double> &verticalLevels ) const
620{
621 Q_UNUSED( verticalLevels )
622 startVerticalLevel = mStartElevation;
623 endVerticalLevel = mEndElevation;
624}
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:56
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:6975