QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 <memory>
19 
20 #include "qgsmesh3daveraging.h"
21 #include "qgsmeshdataprovider.h"
23 
24 // threshold for length intervals, to avoid division by 0
25 static const double eps = 1e-6;
26 
28  : mMethod( method )
29 {
30 }
31 
33 {
34  std::unique_ptr<QgsMesh3dAveragingMethod> ret;
35 
37  elem.attribute( QStringLiteral( "method" ) ).toInt() );
38  switch ( method )
39  {
41  ret.reset( new QgsMeshMultiLevelsAveragingMethod() );
42  break;
44  ret.reset( new QgsMeshSigmaAveragingMethod() );
45  break;
47  ret.reset( new QgsMeshRelativeHeightAveragingMethod() );
48  break;
50  ret.reset( new QgsMeshElevationAveragingMethod() );
51  break;
52  }
53  ret->readXml( elem );
54  return ret.release();
55 }
56 
58 {
59  if ( !block3d.isValid() )
60  return QgsMeshDataBlock();
61 
62  if ( !hasValidInputs() )
63  return QgsMeshDataBlock();
64 
65  bool isVector = block3d.isVector();
66  int count = block3d.count();
68  QVector<double> valuesFaces( isVector ? 2 * count : count, std::numeric_limits<double>::quiet_NaN() );
69  const QVector<int> verticalLevelsCount = block3d.verticalLevelsCount();
70  const QVector<double> verticalLevels = block3d.verticalLevels();
71  const QVector<double> volumeValues = block3d.values();
72 
73  int startVolumeIndex = 0;
74  for ( int faceIndex = 0; faceIndex < count; ++faceIndex )
75  {
76  if ( feedback && feedback->isCanceled() )
77  {
78  return QgsMeshDataBlock();
79  }
80 
81  int volumesBelowFaceCount = verticalLevelsCount[faceIndex];
82  if ( volumesBelowFaceCount <= 0 )
83  continue;
84 
85  int startVerticalLevelIndex = startVolumeIndex + faceIndex;
86  Q_ASSERT( verticalLevels.size() >= startVerticalLevelIndex + volumesBelowFaceCount + 1 );
87  QVector<double> verticalLevelsForFace = verticalLevels.mid( startVerticalLevelIndex, volumesBelowFaceCount + 1 );
88  double faceLevelTop = verticalLevelsForFace[0];
89  double faceLevelBottom = verticalLevelsForFace[verticalLevelsForFace.size() - 1];
90 
91  // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
92  if ( faceLevelTop < faceLevelBottom )
93  {
94  std::swap( faceLevelTop, faceLevelBottom );
95  }
96 
97  double methodLevelTop = std::numeric_limits<double>::quiet_NaN();
98  double methodLevelBottom = std::numeric_limits<double>::quiet_NaN();
99 
100  volumeRangeForFace( methodLevelTop,
101  methodLevelBottom,
102  verticalLevelsForFace );
103 
104  if ( !std::isnan( methodLevelTop ) && !std::isnan( methodLevelBottom ) )
105  {
106  // the level is value below surface, so top level (-0.1m) is usually higher number than bottom level (e.g. -1.2m)
107  if ( methodLevelTop < methodLevelBottom )
108  {
109  std::swap( methodLevelTop, methodLevelBottom );
110  }
111 
112  // check if we are completely outside the limits
113  if ( ( methodLevelTop >= faceLevelBottom ) && ( methodLevelBottom <= faceLevelTop ) )
114  {
115  averageVolumeValuesForFace(
116  faceIndex,
117  volumesBelowFaceCount,
118  startVolumeIndex,
119  methodLevelTop,
120  methodLevelBottom,
121  isVector,
122  verticalLevelsForFace,
123  volumeValues,
124  valuesFaces
125  );
126  }
127  }
128 
129  // move to next face and associated volumes
130  startVolumeIndex += volumesBelowFaceCount;
131  }
132  result.setValues( valuesFaces );
133  return result;
134 }
135 
137 {
138  return mMethod;
139 }
140 
141 void QgsMesh3dAveragingMethod::averageVolumeValuesForFace(
142  int faceIndex,
143  int volumesBelowFaceCount,
144  int startVolumeIndex,
145  double methodLevelTop,
146  double methodLevelBottom,
147  bool isVector,
148  const QVector<double> &verticalLevelsForFace,
149  const QVector<double> &volumeValues,
150  QVector<double> &valuesFaces
151 ) const
152 {
153  double totalAveragedHeight = 0;
154  double nSumX = 0.0;
155  double nSumY = 0.0;
156 
157  // Now go through all volumes below face and check if we need to take that volume into consideration
158  for ( int relativeVolumeIndex = 0; relativeVolumeIndex < volumesBelowFaceCount; ++relativeVolumeIndex )
159  {
160  const int volumeIndex = startVolumeIndex + relativeVolumeIndex;
161  double volumeLevelTop = verticalLevelsForFace[relativeVolumeIndex];
162  double volumeLevelBottom = verticalLevelsForFace[relativeVolumeIndex + 1];
163  if ( volumeLevelTop < volumeLevelBottom )
164  {
165  std::swap( volumeLevelTop, volumeLevelBottom );
166  }
167 
168  const double intersectionLevelTop = std::min( methodLevelTop, volumeLevelTop );
169  const double intersectionLevelBottom = std::max( methodLevelBottom, volumeLevelBottom );
170  const double effectiveInterval = intersectionLevelTop - intersectionLevelBottom;
171 
172  if ( effectiveInterval > eps )
173  {
174  if ( isVector )
175  {
176  const double x = volumeValues[2 * volumeIndex ];
177  const double y = volumeValues[ 2 * volumeIndex + 1 ];
178  if ( ! std::isnan( x ) &&
179  ! std::isnan( y )
180  )
181  {
182  nSumX += x * effectiveInterval;
183  nSumY += y * effectiveInterval;
184  totalAveragedHeight += effectiveInterval;
185  }
186  }
187  else
188  {
189  const double x = volumeValues[ volumeIndex ];
190  if ( ! std::isnan( x ) )
191  {
192  nSumX += x * effectiveInterval;
193  totalAveragedHeight += effectiveInterval;
194  }
195  }
196  }
197  }
198 
199  // calculate average
200  if ( totalAveragedHeight > eps )
201  {
202  if ( isVector )
203  {
204  valuesFaces[2 * faceIndex] = nSumX / totalAveragedHeight;
205  valuesFaces[2 * faceIndex + 1 ] = nSumY / totalAveragedHeight;
206  }
207  else
208  {
209  valuesFaces[faceIndex] = nSumX / totalAveragedHeight;
210  }
211  }
212 }
213 
215 {
216  if ( a )
217  return a->equals( b );
218  else
219  return !b;
220 }
221 
222 QgsMeshMultiLevelsAveragingMethod::QgsMeshMultiLevelsAveragingMethod( int startLevel, int endLevel, bool countedFromTop )
224  , mStartVerticalLevel( startLevel )
225  , mEndVerticalLevel( endLevel )
226  , mCountedFromTop( countedFromTop )
227 {
228  if ( mStartVerticalLevel > mEndVerticalLevel )
229  {
230  std::swap( mStartVerticalLevel, mEndVerticalLevel );
231  }
232 }
233 
236 {
237 }
238 
241  , mStartVerticalLevel( verticalLevel )
242  , mEndVerticalLevel( verticalLevel )
243  , mCountedFromTop( countedFromTop )
244 {
245 }
246 
248 
249 QDomElement QgsMeshMultiLevelsAveragingMethod::writeXml( QDomDocument &doc ) const
250 {
251  QDomElement elem = doc.createElement( QStringLiteral( "multi-vertical-layers-settings" ) );
252  elem.setAttribute( QStringLiteral( "start-layer-index" ), startVerticalLevel() );
253  elem.setAttribute( QStringLiteral( "end-layer-index" ), endVerticalLevel() );
254  return elem;
255 }
256 
257 void QgsMeshMultiLevelsAveragingMethod::readXml( const QDomElement &elem )
258 {
259  QDomElement settings = elem.firstChildElement( QStringLiteral( "multi-vertical-layers-settings" ) );
260  if ( !settings.isNull() )
261  {
262  mStartVerticalLevel = settings.attribute( QStringLiteral( "start-layer-index" ) ).toInt();
263  mEndVerticalLevel = settings.attribute( QStringLiteral( "end-layer-index" ) ).toInt();
264  if ( mStartVerticalLevel > mEndVerticalLevel )
265  {
266  std::swap( mStartVerticalLevel, mEndVerticalLevel );
267  }
268  }
269 }
270 
272 {
273  if ( !other || other->method() != method() )
274  return false;
275 
276  const QgsMeshMultiLevelsAveragingMethod *otherMethod = static_cast<const QgsMeshMultiLevelsAveragingMethod *>( other );
277 
278  return ( otherMethod->startVerticalLevel() == startVerticalLevel() ) &&
279  ( otherMethod->endVerticalLevel() == endVerticalLevel() ) &&
280  ( otherMethod->countedFromTop() == countedFromTop() );
281 }
282 
284 {
286 }
287 
288 
290 {
291  return mStartVerticalLevel;
292 }
293 
295 {
296  return mEndVerticalLevel;
297 }
298 
299 bool QgsMeshMultiLevelsAveragingMethod::hasValidInputs() const
300 {
301  return mStartVerticalLevel >= 1 && mEndVerticalLevel >= mStartVerticalLevel;
302 }
303 
304 void QgsMeshMultiLevelsAveragingMethod::volumeRangeForFace(
305  double &startVerticalLevel,
306  double &endVerticalLevel,
307  const QVector<double> &verticalLevels
308 ) const
309 {
310  Q_ASSERT( mStartVerticalLevel <= mEndVerticalLevel );
311 
312  if ( countedFromTop() )
313  {
314  int startIndex = mStartVerticalLevel - 1;
315  if ( startIndex >= 0 && startIndex < verticalLevels.size() )
316  {
317  startVerticalLevel = verticalLevels[ startIndex ];
318  }
319 
320  if ( mEndVerticalLevel >= 0 && mEndVerticalLevel < verticalLevels.size() )
321  {
322  endVerticalLevel = verticalLevels[ mEndVerticalLevel ];
323  }
324  else
325  {
326  endVerticalLevel = verticalLevels[ verticalLevels.size() - 1 ];
327  }
328  }
329  else
330  {
331  int volumesBelowFaceCount = verticalLevels.size() - 1;
332  int startIndex = volumesBelowFaceCount - mEndVerticalLevel;
333  if ( startIndex >= 0 && startIndex < verticalLevels.size() )
334  {
335  startVerticalLevel = verticalLevels[ startIndex ];
336  }
337  else
338  {
339  startVerticalLevel = verticalLevels[ 0 ];
340  }
341 
342  int endIndex = volumesBelowFaceCount - mStartVerticalLevel + 1;
343  if ( endIndex >= 0 && endIndex < verticalLevels.size() )
344  {
345  endVerticalLevel = verticalLevels[ endIndex ];
346  }
347  }
348 }
349 
352 {
353 }
354 
357  , mStartFraction( startFraction )
358  , mEndFraction( endFraction )
359 {
360  if ( mStartFraction > mEndFraction )
361  {
362  std::swap( mStartFraction, mEndFraction );
363  }
364 }
365 
367 
368 QDomElement QgsMeshSigmaAveragingMethod::writeXml( QDomDocument &doc ) const
369 {
370  QDomElement elem = doc.createElement( QStringLiteral( "sigma-settings" ) );
371  elem.setAttribute( QStringLiteral( "start-fraction" ), startFraction() );
372  elem.setAttribute( QStringLiteral( "end-fraction" ), endFraction() );
373  return elem;
374 }
375 
376 void QgsMeshSigmaAveragingMethod::readXml( const QDomElement &elem )
377 {
378  QDomElement settings = elem.firstChildElement( QStringLiteral( "sigma-settings" ) );
379  if ( !settings.isNull() )
380  {
381  mStartFraction = settings.attribute( QStringLiteral( "start-fraction" ) ).toDouble();
382  mEndFraction = settings.attribute( QStringLiteral( "end-fraction" ) ).toDouble();
383  if ( mStartFraction > mEndFraction )
384  {
385  std::swap( mStartFraction, mEndFraction );
386  }
387  }
388 }
389 
391 {
392  if ( !other || other->method() != method() )
393  return false;
394 
395  const QgsMeshSigmaAveragingMethod *otherMethod = static_cast<const QgsMeshSigmaAveragingMethod *>( other );
396 
397  return qgsDoubleNear( otherMethod->startFraction(), startFraction() ) && qgsDoubleNear( otherMethod->endFraction(), endFraction() ) ;
398 }
399 
401 {
403 }
404 
406 {
407  return mStartFraction;
408 }
409 
411 {
412  return mEndFraction;
413 }
414 
415 bool QgsMeshSigmaAveragingMethod::hasValidInputs() const
416 {
417  return mStartFraction >= 0 && mEndFraction >= mStartFraction && mEndFraction <= 1;
418 }
419 
420 void QgsMeshSigmaAveragingMethod::volumeRangeForFace(
421  double &startVerticalLevel,
422  double &endVerticalLevel,
423  const QVector<double> &verticalLevels
424 ) const
425 {
426  const double top = verticalLevels[ 0 ];
427  const double bot = verticalLevels[ verticalLevels.size() - 1 ];
428  const double diff = top - bot;
429 
430  if ( mStartFraction < 0 )
431  startVerticalLevel = bot;
432  else
433  startVerticalLevel = bot + diff * mStartFraction;
434 
435  if ( mEndFraction > 1 )
436  endVerticalLevel = top;
437  else
438  endVerticalLevel = bot + diff * mEndFraction;
439 }
440 
442 {
443  return mCountedFromTop;
444 }
445 
447 {
448  return mStartVerticalLevel == mEndVerticalLevel;
449 }
450 
451 
454 {
455 }
456 
459  , mStartHeight( startDepth )
460  , mEndHeight( endDepth )
461  , mCountedFromTop( countedFromTop )
462 {
463  if ( mStartHeight > mEndHeight )
464  {
465  std::swap( mStartHeight, mEndHeight );
466  }
467 }
468 
470 
471 QDomElement QgsMeshRelativeHeightAveragingMethod::writeXml( QDomDocument &doc ) const
472 {
473  QDomElement elem = doc.createElement( QStringLiteral( "relative-height-settings" ) );
474  elem.setAttribute( QStringLiteral( "start-height" ), startHeight() );
475  elem.setAttribute( QStringLiteral( "end-height" ), endHeight() );
476  return elem;
477 }
478 
479 void QgsMeshRelativeHeightAveragingMethod::readXml( const QDomElement &elem )
480 {
481  QDomElement settings = elem.firstChildElement( QStringLiteral( "relative-height-settings" ) );
482  if ( !settings.isNull() )
483  {
484  mStartHeight = settings.attribute( QStringLiteral( "start-height" ) ).toDouble();
485  mEndHeight = settings.attribute( QStringLiteral( "end-height" ) ).toDouble();
486  if ( mStartHeight > mEndHeight )
487  {
488  std::swap( mStartHeight, mEndHeight );
489  }
490  }
491 }
492 
494 {
495  if ( !other || other->method() != method() )
496  return false;
497 
498  const QgsMeshRelativeHeightAveragingMethod *otherMethod = static_cast<const QgsMeshRelativeHeightAveragingMethod *>( other );
499 
500  return qgsDoubleNear( otherMethod->startHeight(), startHeight() ) &&
501  qgsDoubleNear( otherMethod->endHeight(), endHeight() ) &&
502  otherMethod->countedFromTop() == countedFromTop();
503 }
504 
506 {
508 }
509 
511 {
512  return mStartHeight;
513 }
514 
516 {
517  return mEndHeight;
518 }
519 
520 bool QgsMeshRelativeHeightAveragingMethod::hasValidInputs() const
521 {
522  return mStartHeight >= 0 && mEndHeight >= mStartHeight;
523 }
524 
525 void QgsMeshRelativeHeightAveragingMethod::volumeRangeForFace(
526  double &startVerticalLevel,
527  double &endVerticalLevel,
528  const QVector<double> &verticalLevels ) const
529 {
530  if ( countedFromTop() )
531  {
532  const double top = verticalLevels[ 0 ];
533  startVerticalLevel = top - mStartHeight;
534  endVerticalLevel = top - mEndHeight;
535  }
536  else
537  {
538  const double bot = verticalLevels[verticalLevels.size() - 1];
539  startVerticalLevel = bot + mStartHeight;
540  endVerticalLevel = bot + mEndHeight;
541  }
542 }
543 
545 {
546  return mCountedFromTop;
547 }
548 
551 {
552 }
553 
556  , mStartElevation( startElevation )
557  , mEndElevation( endElevation )
558 {
559  if ( mEndElevation > mStartElevation )
560  {
561  std::swap( mEndElevation, mStartElevation );
562  }
563 }
564 
566 
567 QDomElement QgsMeshElevationAveragingMethod::writeXml( QDomDocument &doc ) const
568 {
569  QDomElement elem = doc.createElement( QStringLiteral( "elevation-settings" ) );
570  elem.setAttribute( QStringLiteral( "start-elevation" ), startElevation() );
571  elem.setAttribute( QStringLiteral( "end-elevation" ), endElevation() );
572  return elem;
573 }
574 
575 void QgsMeshElevationAveragingMethod::readXml( const QDomElement &elem )
576 {
577  QDomElement settings = elem.firstChildElement( QStringLiteral( "elevation-settings" ) );
578  if ( !settings.isNull() )
579  {
580  mStartElevation = settings.attribute( QStringLiteral( "start-elevation" ) ).toDouble();
581  mEndElevation = settings.attribute( QStringLiteral( "end-elevation" ) ).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 
600 {
602 }
603 
605 {
606  return mStartElevation;
607 }
608 
610 {
611  return mEndElevation;
612 }
613 
614 bool QgsMeshElevationAveragingMethod::hasValidInputs() const
615 {
616  return mStartElevation <= 0.0 && mEndElevation <= mStartElevation;
617 }
618 
619 void QgsMeshElevationAveragingMethod::volumeRangeForFace(
620  double &startVerticalLevel,
621  double &endVerticalLevel,
622  const QVector<double> &verticalLevels ) const
623 {
624  Q_UNUSED( verticalLevels )
625  startVerticalLevel = mStartElevation;
626  endVerticalLevel = mEndElevation;
627 }
Method method() const
Returns type of averaging method.
QgsMeshMultiLevelsAveragingMethod()
Constructs single level averaging method for 1st (top) vertical level.
double startElevation() const
Returns start elevation.
Sigma averages over the values between 0 (bed level) and 1 (surface).
bool equals(const QgsMesh3dAveragingMethod *other) const override
Returns whether method equals to other.
QgsMesh3dAveragingMethod * clone() const override
Clone the instance.
Vector double pairs (x1, y1, x2, y2, ... )
QgsMeshSigmaAveragingMethod()
Constructs the sigma method for whole value range 0-1.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsMeshDataBlock is a block of integers/doubles that can be used to retrieve: active flags (e...
bool isSingleLevel() const
Returns whether the averaging method selects only a single vertical level.
QgsMesh3dDataBlock is a block of 3d stacked mesh data related N faces defined on base mesh frame...
Elevation averaging method averages the values based on range defined absolute value to the model&#39;s d...
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
double endFraction() const
Returns ending fraction.
QVector< int > verticalLevelsCount() const
Returns number of vertical level above 2d faces.
double endElevation() const
Returns end elevation.
bool isVector() const
Whether we store vector values.
Multi level averaging method specifies limits of vertical layers from the top layer down or reversed...
bool isValid() const
Whether the block is valid.
static bool equals(const QgsMesh3dAveragingMethod *a, const QgsMesh3dAveragingMethod *b)
Returns whether two methods equal.
QgsMesh3dAveragingMethod * clone() const override
Clone the instance.
Base class for feedback objects to be used for cancellation of something running in a worker thread...
Definition: qgsfeedback.h:45
int count() const
Number of 2d faces for which the volume data is stored in the block.
static QgsMesh3dAveragingMethod * createFromXml(const QDomElement &elem)
Creates the instance from XML by calling readXml of derived classes.
Relative height averaging method averages the values based on range defined relative to bed elevation...
void readXml(const QDomElement &elem) override
Reads configuration from the given DOM element.
void readXml(const QDomElement &elem) override
Reads configuration from the given DOM element.
Method to average values between 0 (bed level) and 1 (surface)
QVector< double > values() const
Returns the values at volume centers.
Method to average values defined by range of absolute length units to the model&#39;s datum...
bool countedFromTop() const
Returns whether the start and end vertical levels are indexed from top (surface) or bottom (bed) leve...
QgsMeshDataBlock calculate(const QgsMesh3dDataBlock &block3d, QgsFeedback *feedback=nullptr) const
Calculated 2d block values from 3d stacked mesh values.
int startVerticalLevel() const
Returns starting vertical level.
double startFraction() const
Returns starting fraction.
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.
Method
Type of averaging method.
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
bool countedFromTop() const
Returns whether the start and end vertical levels are relative to top (surface) or bottom (bed) level...
QDomElement writeXml(QDomDocument &doc) const override
Writes configuration to a new DOM element.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:55
QgsMesh3dAveragingMethod(Method method)
Ctor.
QgsMeshRelativeHeightAveragingMethod()
Constructs default depth averaging method.
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.
Abstract class to interpolate 3d stacked mesh data to 2d data.
QgsMesh3dAveragingMethod * clone() const override
Clone the instance.
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.
int endVerticalLevel() const
Returns ending vertical level.
double startHeight() const
Returns starting depth/height.
void setValues(const QVector< double > &vals)
Sets values.
Method to average values defined by range of relative length units to the surface or bed level...
double endHeight() const
Returns ending depth/height.
QgsMesh3dAveragingMethod * clone() const override
Clone the instance.
Method to average values from selected vertical layers.
~QgsMeshSigmaAveragingMethod() override
QVector< double > verticalLevels() const
Returns the vertical levels height.