QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsmeshtracerenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmeshtracerenderer.cpp
3  -------------------------
4  begin : November 2019
5  copyright : (C) 2019 by Vincent Cloarec
6  email : vcloarec 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 "qgsmeshtracerenderer.h"
19 #include "qgsmeshlayerrenderer.h"
20 
21 #include <QPointer>
22 
24 
25 #ifndef M_DEG2RAD
26 #define M_DEG2RAD 0.0174532925
27 #endif
28 
29 
30 QgsVector QgsMeshVectorValueInterpolator::vectorValue( const QgsPointXY &point ) const
31 {
32  if ( mCacheFaceIndex != -1 && mCacheFaceIndex < mTriangularMesh.triangles().count() )
33  {
34  QgsVector res = interpolatedValuePrivate( mCacheFaceIndex, point );
35  if ( isVectorValid( res ) )
36  {
37  activeFaceFilter( res, mCacheFaceIndex );
38  return res;
39  }
40  }
41 
42  //point is not on the face associated with mCacheIndex --> search for the face containing the point
43  QList<int> potentialFaceIndexes = mTriangularMesh.faceIndexesForRectangle( QgsRectangle( point, point ) );
44  mCacheFaceIndex = -1;
45  for ( const int faceIndex : potentialFaceIndexes )
46  {
47  QgsVector res = interpolatedValuePrivate( faceIndex, point );
48  if ( isVectorValid( res ) )
49  {
50  mCacheFaceIndex = faceIndex;
51  activeFaceFilter( res, mCacheFaceIndex );
52  return res;
53  }
54  }
55 
56  //--> no face found return non valid vector
57  return ( QgsVector( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() ) );
58 
59 }
60 
61 QgsMeshVectorValueInterpolator &QgsMeshVectorValueInterpolator::operator=( const QgsMeshVectorValueInterpolator &other )
62 {
63  mTriangularMesh = other.mTriangularMesh;
64  mDatasetValues = other.mDatasetValues;
65  mActiveFaceFlagValues = other.mActiveFaceFlagValues;
66  mFaceCache = other.mFaceCache;
67  mCacheFaceIndex = other.mCacheFaceIndex;
68  mUseScalarActiveFaceFlagValues = other.mUseScalarActiveFaceFlagValues;
69 
70  return *this;
71 }
72 
73 QgsMeshVectorValueInterpolatorFromVertex::
74 QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues ):
75  QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
76 {
77 
78 }
79 
80 QgsMeshVectorValueInterpolatorFromVertex::
81 QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh,
82  const QgsMeshDataBlock &datasetVectorValues,
83  const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
84  QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
85 {
86 
87 }
88 
89 QgsMeshVectorValueInterpolatorFromVertex::QgsMeshVectorValueInterpolatorFromVertex( const QgsMeshVectorValueInterpolatorFromVertex &other ):
90  QgsMeshVectorValueInterpolator( other )
91 {}
92 
93 QgsMeshVectorValueInterpolatorFromVertex *QgsMeshVectorValueInterpolatorFromVertex::clone()
94 {
95  return new QgsMeshVectorValueInterpolatorFromVertex( *this );
96 }
97 
98 QgsMeshVectorValueInterpolatorFromVertex &QgsMeshVectorValueInterpolatorFromVertex::
99 operator=( const QgsMeshVectorValueInterpolatorFromVertex &other )
100 {
101  QgsMeshVectorValueInterpolator::operator=( other );
102  return ( *this );
103 }
104 
105 QgsVector QgsMeshVectorValueInterpolatorFromVertex::interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const
106 {
107  QgsMeshFace face = mTriangularMesh.triangles().at( faceIndex );
108 
109  QgsPoint p1 = mTriangularMesh.vertices().at( face.at( 0 ) );
110  QgsPoint p2 = mTriangularMesh.vertices().at( face.at( 1 ) );
111  QgsPoint p3 = mTriangularMesh.vertices().at( face.at( 2 ) );
112 
113  QgsVector v1 = QgsVector( mDatasetValues.value( face.at( 0 ) ).x(),
114  mDatasetValues.value( face.at( 0 ) ).y() );
115 
116  QgsVector v2 = QgsVector( mDatasetValues.value( face.at( 1 ) ).x(),
117  mDatasetValues.value( face.at( 1 ) ).y() );
118 
119  QgsVector v3 = QgsVector( mDatasetValues.value( face.at( 2 ) ).x(),
120  mDatasetValues.value( face.at( 2 ) ).y() );
121 
122  return QgsMeshLayerUtils::interpolateVectorFromVerticesData(
123  p1,
124  p2,
125  p3,
126  v1,
127  v2,
128  v3,
129  point );
130 }
131 
132 QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsTriangularMesh &triangularMesh,
133  const QgsMeshDataBlock &datasetVectorValues ):
134  mTriangularMesh( triangularMesh ),
135  mDatasetValues( datasetVectorValues ),
136  mUseScalarActiveFaceFlagValues( false )
137 {}
138 
139 QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsTriangularMesh &triangularMesh,
140  const QgsMeshDataBlock &datasetVectorValues,
141  const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
142  mTriangularMesh( triangularMesh ),
143  mDatasetValues( datasetVectorValues ),
144  mActiveFaceFlagValues( scalarActiveFaceFlagValues ),
145  mUseScalarActiveFaceFlagValues( true )
146 {}
147 
148 QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsMeshVectorValueInterpolator &other ):
149  mTriangularMesh( other.mTriangularMesh ),
150  mDatasetValues( other.mDatasetValues ),
151  mActiveFaceFlagValues( other.mActiveFaceFlagValues ),
152  mFaceCache( other.mFaceCache ),
153  mCacheFaceIndex( other.mCacheFaceIndex ),
154  mUseScalarActiveFaceFlagValues( other.mUseScalarActiveFaceFlagValues )
155 {}
156 
157 void QgsMeshVectorValueInterpolator::updateCacheFaceIndex( const QgsPointXY &point ) const
158 {
159  if ( ! QgsMeshUtils::isInTriangleFace( point, mFaceCache, mTriangularMesh.vertices() ) )
160  {
161  mCacheFaceIndex = mTriangularMesh.faceIndexForPoint_v2( point );
162  }
163 }
164 
165 bool QgsMeshVectorValueInterpolator::isVectorValid( const QgsVector &v ) const
166 {
167  return !( std::isnan( v.x() ) || std::isnan( v.y() ) );
168 
169 }
170 
171 void QgsMeshVectorValueInterpolator::activeFaceFilter( QgsVector &vector, int faceIndex ) const
172 {
173  if ( mUseScalarActiveFaceFlagValues && ! mActiveFaceFlagValues.active( mTriangularMesh.trianglesToNativeFaces()[faceIndex] ) )
174  vector = QgsVector( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() ) ;
175 }
176 
177 QSize QgsMeshStreamField::size() const
178 {
179  return mFieldSize;
180 }
181 
182 QPoint QgsMeshStreamField::topLeft() const
183 {
184  return mFieldTopLeftInDeviceCoordinates;
185 }
186 
187 int QgsMeshStreamField::resolution() const
188 {
189  return mFieldResolution;
190 }
191 
192 QgsPointXY QgsMeshStreamField::positionToMapCoordinates( const QPoint &pixelPosition, const QgsPointXY &positionInPixel )
193 {
194  QgsPointXY mapPoint = mMapToFieldPixel.toMapCoordinates( pixelPosition );
195  mapPoint = mapPoint + QgsVector( positionInPixel.x() * mMapToFieldPixel.mapUnitsPerPixel(),
196  positionInPixel.y() * mMapToFieldPixel.mapUnitsPerPixel() );
197  return mapPoint;
198 }
199 
200 QgsMeshStreamField::QgsMeshStreamField(
201  const QgsTriangularMesh &triangularMesh,
202  const QgsMeshDataBlock &dataSetVectorValues,
203  const QgsMeshDataBlock &scalarActiveFaceFlagValues,
204  const QgsRectangle &layerExtent,
205  double magnitudeMaximum, bool dataIsOnVertices,
206  const QgsRenderContext &rendererContext,
207  const QgsInterpolatedLineColor &vectorColoring,
208  int resolution ):
209  mFieldResolution( resolution ),
210  mVectorColoring( vectorColoring ),
211  mLayerExtent( layerExtent ),
212  mMaximumMagnitude( magnitudeMaximum ),
213  mRenderContext( rendererContext )
214 {
215  if ( dataIsOnVertices )
216  {
217  if ( scalarActiveFaceFlagValues.isValid() )
218  mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromVertex( triangularMesh,
219  dataSetVectorValues,
220  scalarActiveFaceFlagValues ) );
221  else
222  mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromVertex( triangularMesh,
223  dataSetVectorValues ) );
224  }
225  else
226  {
227  if ( scalarActiveFaceFlagValues.isValid() )
228  mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromFace( triangularMesh,
229  dataSetVectorValues,
230  scalarActiveFaceFlagValues ) );
231  else
232  mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromFace( triangularMesh,
233  dataSetVectorValues ) );
234  }
235 }
236 
237 QgsMeshStreamField::QgsMeshStreamField( const QgsMeshStreamField &other ):
238  mFieldSize( other.mFieldSize ),
239  mFieldResolution( other.mFieldResolution ),
240  mPen( other.mPen ),
241  mTraceImage( other.mTraceImage ),
242  mMapToFieldPixel( other.mMapToFieldPixel ),
243  mVectorColoring( other.mVectorColoring ),
244  mPixelFillingCount( other.mPixelFillingCount ),
245  mMaxPixelFillingCount( other.mMaxPixelFillingCount ),
246  mLayerExtent( other.mLayerExtent ),
247  mMapExtent( other.mMapExtent ),
248  mFieldTopLeftInDeviceCoordinates( other.mFieldTopLeftInDeviceCoordinates ),
249  mValid( other.mValid ),
250  mMaximumMagnitude( other.mMaximumMagnitude ),
251  mPixelFillingDensity( other.mPixelFillingDensity ),
252  mMinMagFilter( other.mMinMagFilter ),
253  mMaxMagFilter( other.mMaxMagFilter ),
254  mRenderContext( other.mRenderContext ),
255  mMinimizeFieldSize( other.mMinimizeFieldSize )
256 {
257  mPainter.reset( new QPainter( &mTraceImage ) );
258  mVectorValueInterpolator =
259  std::unique_ptr<QgsMeshVectorValueInterpolator>( other.mVectorValueInterpolator->clone() );
260 }
261 
262 QgsMeshStreamField::~QgsMeshStreamField()
263 {
264  if ( mPainter )
265  mPainter->end();
266 }
267 
268 void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
269 {
270  mMapExtent = renderContext.mapExtent();
271  const QgsMapToPixel &deviceMapToPixel = renderContext.mapToPixel();
272  QgsRectangle layerExtent;
273  try
274  {
275  layerExtent = renderContext.coordinateTransform().transform( mLayerExtent );
276  }
277  catch ( QgsCsException &cse )
278  {
279  Q_UNUSED( cse )
280  //if the transform fails, consider the whole map
281  layerExtent = mMapExtent;
282  }
283 
284  QgsRectangle interestZoneExtent;
285  if ( mMinimizeFieldSize )
286  interestZoneExtent = layerExtent.intersect( mMapExtent );
287  else
288  interestZoneExtent = mMapExtent;
289 
290  if ( interestZoneExtent == QgsRectangle() )
291  {
292  mValid = false;
293  mFieldSize = QSize();
294  mFieldTopLeftInDeviceCoordinates = QPoint();
295  initField();
296  return;
297  }
298 
299 
300  QgsRectangle fieldInterestZoneInDeviceCoordinates = QgsMeshLayerUtils::boundingBoxToScreenRectangle( deviceMapToPixel, interestZoneExtent );
301  mFieldTopLeftInDeviceCoordinates = QPoint( int( fieldInterestZoneInDeviceCoordinates.xMinimum() ), int( fieldInterestZoneInDeviceCoordinates.yMinimum() ) );
302  int fieldWidthInDeviceCoordinate = int( fieldInterestZoneInDeviceCoordinates.width() );
303  int fieldHeightInDeviceCoordinate = int ( fieldInterestZoneInDeviceCoordinates.height() );
304 
305  int fieldWidth = int( fieldWidthInDeviceCoordinate / mFieldResolution );
306  int fieldHeight = int( fieldHeightInDeviceCoordinate / mFieldResolution );
307 
308  //increase the field size if this size is not adjusted to extent of zone of interest in device coordinates
309  if ( fieldWidthInDeviceCoordinate % mFieldResolution > 0 )
310  fieldWidth++;
311  if ( fieldHeightInDeviceCoordinate % mFieldResolution > 0 )
312  fieldHeight++;
313 
314  if ( fieldWidth == 0 || fieldHeight == 0 )
315  {
316  mFieldSize = QSize();
317  }
318  else
319  {
320  mFieldSize.setWidth( fieldWidth );
321  mFieldSize.setHeight( fieldHeight );
322  }
323 
324  double mapUnitPerFieldPixel;
325  if ( interestZoneExtent.width() > 0 )
326  mapUnitPerFieldPixel = deviceMapToPixel.mapUnitsPerPixel() * mFieldResolution * mFieldSize.width() / ( fieldWidthInDeviceCoordinate / mFieldResolution ) ;
327  else
328  mapUnitPerFieldPixel = 1e-8;
329 
330  int fieldRightDevice = mFieldTopLeftInDeviceCoordinates.x() + mFieldSize.width() * mFieldResolution;
331  int fieldBottomDevice = mFieldTopLeftInDeviceCoordinates.y() + mFieldSize.height() * mFieldResolution;
332  QgsPointXY fieldRightBottomMap = deviceMapToPixel.toMapCoordinates( fieldRightDevice, fieldBottomDevice );
333 
334  int fieldTopDevice = mFieldTopLeftInDeviceCoordinates.x();
335  int fieldLeftDevice = mFieldTopLeftInDeviceCoordinates.y();
336  QgsPointXY fieldTopLeftMap = deviceMapToPixel.toMapCoordinates( fieldTopDevice, fieldLeftDevice );
337 
338  double xc = ( fieldRightBottomMap.x() + fieldTopLeftMap.x() ) / 2;
339  double yc = ( fieldTopLeftMap.y() + fieldRightBottomMap.y() ) / 2;
340 
341  mMapToFieldPixel = QgsMapToPixel( mapUnitPerFieldPixel,
342  xc,
343  yc,
344  fieldWidth,
345  fieldHeight,
346  deviceMapToPixel.mapRotation()
347  );
348 
349  initField();
350  mValid = true;
351 }
352 
353 void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext, int resolution )
354 {
355  if ( renderContext.mapExtent() == mMapExtent && resolution == mFieldResolution )
356  return;
357  mFieldResolution = resolution;
358 
359  updateSize( renderContext );
360 }
361 
362 bool QgsMeshStreamField::isValid() const
363 {
364  return mValid;
365 }
366 
367 void QgsMeshStreamField::addTrace( QgsPointXY startPoint )
368 {
369  addTrace( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint() );
370 }
371 
372 
373 void QgsMeshStreamField::addRandomTraces()
374 {
375  if ( mMaximumMagnitude > 0 )
376  while ( mPixelFillingCount < mMaxPixelFillingCount && !mRenderContext.renderingStopped() )
377  addRandomTrace();
378 }
379 
380 void QgsMeshStreamField::addRandomTrace()
381 {
382  if ( !mValid )
383  return;
384 
385  int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) ) ;
386  int yRandom = 1 + std::rand() / int ( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) ) ;
387  addTrace( QPoint( xRandom, yRandom ) );
388 }
389 
390 void QgsMeshStreamField::addGriddedTraces( int dx, int dy )
391 {
392  int i = 0 ;
393  while ( i < mFieldSize.width() && !mRenderContext.renderingStopped() )
394  {
395  int j = 0 ;
396  while ( j < mFieldSize.height() && !mRenderContext.renderingStopped() )
397  {
398  addTrace( QPoint( i, j ) );
399  j += dy;
400  }
401  i += dx;
402  }
403 }
404 
405 void QgsMeshStreamField::addTracesOnMesh( const QgsTriangularMesh &mesh, const QgsRectangle &extent )
406 {
407  QList<int> facesInExtent = mesh.faceIndexesForRectangle( extent );
408  QSet<int> vertices;
409  for ( auto f : std::as_const( facesInExtent ) )
410  {
411  auto face = mesh.triangles().at( f );
412  for ( auto i : std::as_const( face ) )
413  vertices.insert( i );
414  }
415 
416  for ( auto i : std::as_const( vertices ) )
417  {
418  addTrace( mesh.vertices().at( i ) );
419  }
420 }
421 
422 void QgsMeshStreamField::addTrace( QPoint startPixel )
423 {
424  //This is where each traces are constructed
425  if ( !mPainter )
426  return;
427 
428  if ( isTraceExists( startPixel ) || isTraceOutside( startPixel ) )
429  return;
430 
431  if ( !mVectorValueInterpolator )
432  return;
433 
434  if ( !( mMaximumMagnitude > 0 ) )
435  return;
436 
437  mPainter->setPen( mPen );
438 
439  //position in the pixelField
440  double x1 = 0;
441  double y1 = 0;
442 
443  std::list<QPair<QPoint, FieldData>> chunkTrace;
444 
445  QPoint currentPixel = startPixel;
446  QgsVector vector;
447  FieldData data;
448  data.time = 1;
449 
450  while ( !mRenderContext.renderingStopped() )
451  {
452  QgsPointXY mapPosition = positionToMapCoordinates( currentPixel, QgsPointXY( x1, y1 ) );
453  vector = mVectorValueInterpolator->vectorValue( mapPosition ) ;
454 
455  if ( std::isnan( vector.x() ) || std::isnan( vector.y() ) )
456  {
457  mPixelFillingCount++;
458  setChunkTrace( chunkTrace );
459  drawChunkTrace( chunkTrace );
460  break;
461  }
462 
463  /* nondimensional value : Vu=2 when the particle need dt=1 to go through a pixel with the mMagMax magnitude
464  * The nondimensional size of the side of a pixel is 2
465  */
466  vector = vector.rotateBy( -mMapToFieldPixel.mapRotation() * M_DEG2RAD );
467  QgsVector vu = vector / mMaximumMagnitude * 2;
468  data.magnitude = vector.length();
469 
470  double Vx = vu.x();
471  double Vy = vu.y();
472  double Vu = data.magnitude / mMaximumMagnitude * 2; //nondimensional vector magnitude
473 
474  if ( qgsDoubleNear( Vu, 0 ) )
475  {
476  // no trace anymore
477  addPixelToChunkTrace( currentPixel, data, chunkTrace );
478  simplifyChunkTrace( chunkTrace );
479  setChunkTrace( chunkTrace );
480  drawChunkTrace( chunkTrace );
481  break;
482  }
483 
484  //calculates where the particle will be after dt=1,
485  QgsPointXY nextPosition = QgsPointXY( x1, y1 ) + vu;
486  int incX = 0;
487  int incY = 0;
488  if ( nextPosition.x() > 1 )
489  incX = +1;
490  if ( nextPosition.x() < -1 )
491  incX = -1;
492  if ( nextPosition.y() > 1 )
493  incY = +1;
494  if ( nextPosition.y() < -1 )
495  incY = -1;
496 
497  double x2, y2;
498 
499  if ( incX != 0 || incY != 0 )
500  {
501  data.directionX = incX;
502  data.directionY = -incY;
503  //the particule leave the current pixel --> store pixels, calculates where the particle is and change the current pixel
504  if ( chunkTrace.empty() )
505  {
506  storeInField( QPair<QPoint, FieldData>( currentPixel, data ) );
507  }
508  if ( addPixelToChunkTrace( currentPixel, data, chunkTrace ) )
509  {
510  setChunkTrace( chunkTrace );
511  drawChunkTrace( chunkTrace );
512  clearChunkTrace( chunkTrace );
513  }
514 
515  data.time = 1;
516  currentPixel += QPoint( incX, -incY );
517  x1 = nextPosition.x() - 2 * incX;
518  y1 = nextPosition.y() - 2 * incY;
519  }
520  else
521  {
522  /*the particule still in the pixel --> "push" the position with the vector value to join a border
523  * and calculate the time spent to go to this border
524  */
525  if ( qgsDoubleNear( Vy, 0 ) )
526  {
527  y2 = y1;
528  if ( Vx > 0 )
529  incX = +1;
530  else
531  incX = -1;
532 
533  x2 = incX ;
534  }
535  else if ( qgsDoubleNear( Vx, 0 ) )
536  {
537  x2 = x1;
538  if ( Vy > 0 )
539  incY = +1;
540  else
541  incY = -1;
542 
543  y2 = incY ;
544  }
545  else
546  {
547  if ( Vy > 0 )
548  x2 = x1 + ( 1 - y1 ) * Vx / fabs( Vy ) ;
549  else
550  x2 = x1 + ( 1 + y1 ) * Vx / fabs( Vy ) ;
551  if ( Vx > 0 )
552  y2 = y1 + ( 1 - x1 ) * Vy / fabs( Vx ) ;
553  else
554  y2 = y1 + ( 1 + x1 ) * Vy / fabs( Vx ) ;
555 
556  if ( x2 >= 1 )
557  {
558  x2 = 1;
559  incX = +1;
560  }
561  if ( x2 <= -1 )
562  {
563  x2 = -1;
564  incX = -1;
565  }
566  if ( y2 >= 1 )
567  {
568  y2 = 1;
569  incY = +1;
570  }
571  if ( y2 <= -1 )
572  {
573  y2 = -1;
574  incY = -1;
575  }
576  }
577 
578  //calculate distance
579  double dx = x2 - x1;
580  double dy = y2 - y1;
581  double dl = sqrt( dx * dx + dy * dy );
582 
583  data.time += dl / Vu ; //adimensional time step : this the time needed to go to the border of the pixel
584  if ( data.time > 10000 ) //Guard to prevent that the particle never leave the pixel
585  {
586  addPixelToChunkTrace( currentPixel, data, chunkTrace );
587  setChunkTrace( chunkTrace );
588  drawChunkTrace( chunkTrace );
589  break;
590  }
591  x1 = x2;
592  y1 = y2;
593  }
594 
595  //test if the new current pixel is already defined, if yes no need to continue
596  if ( isTraceExists( currentPixel ) )
597  {
598  //Set the pixel in the chunk before adding the current pixel because this pixel is already defined
599  setChunkTrace( chunkTrace );
600  addPixelToChunkTrace( currentPixel, data, chunkTrace );
601  drawChunkTrace( chunkTrace );
602  break;
603  }
604 
605  if ( isTraceOutside( currentPixel ) )
606  {
607  setChunkTrace( chunkTrace );
608  drawChunkTrace( chunkTrace );
609  break;
610  }
611  }
612 }
613 
614 void QgsMeshStreamField::setResolution( int width )
615 {
616  mFieldResolution = width;
617 }
618 
619 QSize QgsMeshStreamField::imageSize() const
620 {
621  return mFieldSize * mFieldResolution;
622 }
623 
624 QPointF QgsMeshStreamField::fieldToDevice( const QPoint &pixel ) const
625 {
626  QPointF p( pixel );
627  p = mFieldResolution * p + QPointF( mFieldResolution - 1, mFieldResolution - 1 ) / 2;
628  return p;
629 }
630 
631 bool QgsMeshStreamField::addPixelToChunkTrace( QPoint &pixel,
632  QgsMeshStreamField::FieldData &data,
633  std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
634 {
635  chunkTrace.emplace_back( pixel, data );
636  if ( chunkTrace.size() == 3 )
637  {
638  simplifyChunkTrace( chunkTrace );
639  return true;
640  }
641  return false;
642 }
643 
644 void QgsMeshStreamlinesField::initField()
645 {
646  mField = QVector<bool>( mFieldSize.width() * mFieldSize.height(), false );
647  initImage();
648 }
649 
650 QgsMeshStreamlinesField::QgsMeshStreamlinesField( const QgsTriangularMesh &triangularMesh,
651  const QgsMeshDataBlock &datasetVectorValues,
652  const QgsMeshDataBlock &scalarActiveFaceFlagValues,
653  const QgsRectangle &layerExtent,
654  double magMax,
655  bool dataIsOnVertices,
656  QgsRenderContext &rendererContext,
657  const QgsInterpolatedLineColor vectorColoring ):
658  QgsMeshStreamField( triangularMesh,
659  datasetVectorValues,
660  scalarActiveFaceFlagValues,
661  layerExtent,
662  magMax,
663  dataIsOnVertices,
664  rendererContext,
665  vectorColoring )
666 {}
667 
668 QgsMeshStreamlinesField::QgsMeshStreamlinesField( const QgsMeshStreamlinesField &other ):
669  QgsMeshStreamField( other ),
670  mField( other.mField )
671 {}
672 
673 QgsMeshStreamlinesField &QgsMeshStreamlinesField::operator=( const QgsMeshStreamlinesField &other )
674 {
675  QgsMeshStreamField::operator=( other );
676  mField = other.mField;
677  return *this;
678 }
679 
680 void QgsMeshStreamlinesField::storeInField( const QPair<QPoint, FieldData> pixelData )
681 {
682  int i = pixelData.first.x();
683  int j = pixelData.first.y();
684  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
685  {
686  mField[j * mFieldSize.width() + i] = true;
687  }
688 }
689 
690 void QgsMeshStreamField::setChunkTrace( std::list<QPair<QPoint, FieldData> > &chunkTrace )
691 {
692  auto p = chunkTrace.begin();
693  while ( p != chunkTrace.end() )
694  {
695  storeInField( ( *p ) );
696  mPixelFillingCount++;
697  ++p;
698  }
699 }
700 
701 void QgsMeshStreamlinesField::drawChunkTrace( const std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
702 {
703  auto p1 = chunkTrace.begin();
704  auto p2 = p1;
705  p2++;
706  while ( p2 != chunkTrace.end() )
707  {
708  double mag1 = ( *p1 ).second.magnitude;
709  double mag2 = ( *p2 ).second.magnitude;
710  if ( filterMag( mag1 ) && filterMag( mag2 ) )
711  {
712  QPen pen = mPainter->pen();
713  pen.setColor( mVectorColoring.color( ( mag1 + mag2 ) / 2 ) );
714  mPainter->setPen( pen );
715  mPainter->drawLine( fieldToDevice( ( *p1 ).first ), fieldToDevice( ( *p2 ).first ) );
716  }
717 
718  p1++;
719  p2++;
720  }
721 }
722 
723 void QgsMeshStreamField::clearChunkTrace( std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
724 {
725  auto one_before_end = std::prev( chunkTrace.end() );
726  chunkTrace.erase( chunkTrace.begin(), one_before_end );
727 }
728 
729 void QgsMeshStreamField::simplifyChunkTrace( std::list<QPair<QPoint, FieldData> > &shunkTrace )
730 {
731  if ( shunkTrace.size() != 3 )
732  return;
733 
734  auto ip3 = shunkTrace.begin();
735  auto ip1 = ip3++;
736  auto ip2 = ip3++;
737 
738  while ( ip3 != shunkTrace.end() && ip2 != shunkTrace.end() )
739  {
740  QPoint v1 = ( *ip1 ).first - ( *ip2 ).first;
741  QPoint v2 = ( *ip2 ).first - ( *ip3 ).first;
742  if ( v1.x()*v2.x() + v1.y()*v2.y() == 0 )
743  {
744  ( *ip1 ).second.time += ( ( *ip2 ).second.time ) / 2;
745  ( *ip3 ).second.time += ( ( *ip2 ).second.time ) / 2;
746  ( *ip1 ).second.directionX += ( *ip2 ).second.directionX;
747  ( *ip1 ).second.directionY += ( *ip2 ).second.directionY;
748  shunkTrace.erase( ip2 );
749  }
750  ip1 = ip3++;
751  ip2 = ip3++;
752  }
753 }
754 
755 bool QgsMeshStreamlinesField::isTraceExists( const QPoint &pixel ) const
756 {
757  int i = pixel.x();
758  int j = pixel.y();
759  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
760  {
761  return mField[j * mFieldSize.width() + i];
762  }
763 
764  return false;
765 }
766 
767 bool QgsMeshStreamField::isTraceOutside( const QPoint &pixel ) const
768 {
769  int i = pixel.x();
770  int j = pixel.y();
771  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
772  {
773  return false;
774  }
775  return true;
776 }
777 
778 void QgsMeshStreamField::setMinimizeFieldSize( bool minimizeFieldSize )
779 {
780  mMinimizeFieldSize = minimizeFieldSize;
781 }
782 
783 QgsMeshStreamField &QgsMeshStreamField::operator=( const QgsMeshStreamField &other )
784 {
785  mFieldSize = other.mFieldSize ;
786  mFieldResolution = other.mFieldResolution;
787  mPen = other.mPen;
788  mTraceImage = other.mTraceImage ;
789  mMapToFieldPixel = other.mMapToFieldPixel ;
790  mVectorColoring = other.mVectorColoring;
791  mPixelFillingCount = other.mPixelFillingCount ;
792  mMaxPixelFillingCount = other.mMaxPixelFillingCount ;
793  mLayerExtent = other.mLayerExtent ;
794  mMapExtent = other.mMapExtent;
795  mFieldTopLeftInDeviceCoordinates = other.mFieldTopLeftInDeviceCoordinates ;
796  mValid = other.mValid ;
797  mMaximumMagnitude = other.mMaximumMagnitude ;
798  mPixelFillingDensity = other.mPixelFillingDensity ;
799  mMinMagFilter = other.mMinMagFilter ;
800  mMaxMagFilter = other.mMaxMagFilter ;
801  mMinimizeFieldSize = other.mMinimizeFieldSize ;
802  mVectorValueInterpolator =
803  std::unique_ptr<QgsMeshVectorValueInterpolator>( other.mVectorValueInterpolator->clone() );
804 
805  mPainter.reset( new QPainter( &mTraceImage ) );
806 
807  return ( *this );
808 }
809 
810 void QgsMeshStreamField::initImage()
811 {
812 
813  mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
814  mTraceImage.fill( 0X00000000 );
815 
816  mPainter.reset( new QPainter( &mTraceImage ) );
817  mPainter->setRenderHint( QPainter::Antialiasing, true );
818  mPainter->setPen( mPen );
819 }
820 
821 bool QgsMeshStreamField::filterMag( double value ) const
822 {
823  return ( mMinMagFilter < 0 || value > mMinMagFilter ) && ( mMaxMagFilter < 0 || value < mMaxMagFilter );
824 }
825 
826 QImage QgsMeshStreamField::image()
827 {
828  return mTraceImage.scaled( mFieldSize * mFieldResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
829 }
830 
831 void QgsMeshStreamField::setPixelFillingDensity( double maxFilling )
832 {
833  mPixelFillingDensity = maxFilling;
834  mMaxPixelFillingCount = int( mPixelFillingDensity * mFieldSize.width() * mFieldSize.height() );
835 }
836 
837 void QgsMeshStreamField::setColor( QColor color )
838 {
839  mPen.setColor( color );
840 }
841 
842 void QgsMeshStreamField::setLineWidth( double width )
843 {
844  mPen.setWidthF( width );
845 }
846 
847 void QgsMeshStreamField::setFilter( double min, double max )
848 {
849  mMinMagFilter = min;
850  mMaxMagFilter = max;
851 }
852 
853 QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues ):
854  QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
855 {}
856 
857 QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
858  QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
859 {}
860 
861 QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsMeshVectorValueInterpolatorFromFace &other ):
862  QgsMeshVectorValueInterpolator( other )
863 {}
864 
865 QgsMeshVectorValueInterpolatorFromFace *QgsMeshVectorValueInterpolatorFromFace::clone()
866 {
867  return new QgsMeshVectorValueInterpolatorFromFace( *this );
868 }
869 
870 QgsMeshVectorValueInterpolatorFromFace &QgsMeshVectorValueInterpolatorFromFace::operator=( const QgsMeshVectorValueInterpolatorFromFace &other )
871 {
872  QgsMeshVectorValueInterpolator::operator=( other );
873  return ( *this );
874 }
875 
876 QgsVector QgsMeshVectorValueInterpolatorFromFace::interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const
877 {
878  QgsMeshFace face = mTriangularMesh.triangles().at( faceIndex );
879 
880  QgsPoint p1 = mTriangularMesh.vertices().at( face.at( 0 ) );
881  QgsPoint p2 = mTriangularMesh.vertices().at( face.at( 1 ) );
882  QgsPoint p3 = mTriangularMesh.vertices().at( face.at( 2 ) );
883 
884  QgsVector vect = QgsVector( mDatasetValues.value( mTriangularMesh.trianglesToNativeFaces().at( faceIndex ) ).x(),
885  mDatasetValues.value( mTriangularMesh.trianglesToNativeFaces().at( faceIndex ) ).y() );
886 
887  return QgsMeshLayerUtils::interpolateVectorFromFacesData(
888  p1,
889  p2,
890  p3,
891  vect,
892  point );
893 }
894 
895 QgsMeshVectorStreamlineRenderer::QgsMeshVectorStreamlineRenderer(
896  const QgsTriangularMesh &triangularMesh,
897  const QgsMeshDataBlock &dataSetVectorValues,
898  const QgsMeshDataBlock &scalarActiveFaceFlagValues,
899  bool dataIsOnVertices,
900  const QgsMeshRendererVectorSettings &settings,
901  QgsRenderContext &rendererContext,
902  const QgsRectangle &layerExtent, double magMax ):
903  mRendererContext( rendererContext )
904 {
905  mStreamlineField.reset( new QgsMeshStreamlinesField( triangularMesh,
906  dataSetVectorValues,
907  scalarActiveFaceFlagValues,
908  layerExtent,
909  magMax,
910  dataIsOnVertices,
911  rendererContext,
912  settings.vectorStrokeColoring() ) );
913 
914  mStreamlineField->updateSize( rendererContext );
915  mStreamlineField->setPixelFillingDensity( settings.streamLinesSettings().seedingDensity() );
916  mStreamlineField->setLineWidth( rendererContext.convertToPainterUnits( settings.lineWidth(),
917  QgsUnitTypes::RenderUnit::RenderMillimeters ) ) ;
918  mStreamlineField->setColor( settings.color() );
919  mStreamlineField->setFilter( settings.filterMin(), settings.filterMax() );
920 
921  switch ( settings.streamLinesSettings().seedingMethod() )
922  {
924  if ( settings.isOnUserDefinedGrid() )
925  mStreamlineField->addGriddedTraces( settings.userGridCellWidth(), settings.userGridCellHeight() );
926  else
927  mStreamlineField->addTracesOnMesh( triangularMesh, rendererContext.mapExtent() );
928  break;
930  mStreamlineField->addRandomTraces();
931  break;
932  }
933 }
934 
935 void QgsMeshVectorStreamlineRenderer::draw()
936 {
937  if ( mRendererContext.renderingStopped() )
938  return;
939  mRendererContext.painter()->drawImage( mStreamlineField->topLeft(), mStreamlineField->image() );
940 }
941 
942 QgsMeshParticleTracesField::QgsMeshParticleTracesField( const QgsTriangularMesh &triangularMesh,
943  const QgsMeshDataBlock &datasetVectorValues,
944  const QgsMeshDataBlock &scalarActiveFaceFlagValues,
945  const QgsRectangle &layerExtent,
946  double magMax,
947  bool dataIsOnVertices,
948  const QgsRenderContext &rendererContext,
949  const QgsInterpolatedLineColor vectorColoring ):
950  QgsMeshStreamField( triangularMesh,
951  datasetVectorValues,
952  scalarActiveFaceFlagValues,
953  layerExtent,
954  magMax,
955  dataIsOnVertices,
956  rendererContext,
957  vectorColoring )
958 {
959  std::srand( uint( ::time( nullptr ) ) );
960  mPen.setCapStyle( Qt::RoundCap );
961 }
962 
963 QgsMeshParticleTracesField::QgsMeshParticleTracesField( const QgsMeshParticleTracesField &other ):
964  QgsMeshStreamField( other ),
965  mTimeField( other.mTimeField ),
966  mMagnitudeField( other.mMagnitudeField ),
967  mDirectionField( other.mDirectionField ),
968  mParticles( other.mParticles ),
969  mStumpImage( other.mStumpImage ),
970  mTimeStep( other.mTimeStep ),
971  mParticlesLifeTime( other.mParticlesLifeTime ),
972  mParticlesCount( other.mParticlesCount ),
973  mTailFactor( other.mTailFactor ),
974  mParticleColor( other.mParticleColor ),
975  mParticleSize( other.mParticleSize ),
976  mStumpFactor( other.mStumpFactor )
977 {}
978 
979 void QgsMeshParticleTracesField::addParticle( const QPoint &startPoint, double lifeTime )
980 {
981  addTrace( startPoint );
982  if ( time( startPoint ) > 0 )
983  {
984  QgsMeshTraceParticle p;
985  p.lifeTime = lifeTime;
986  p.position = startPoint;
987  mParticles.append( p );
988  }
989 
990 }
991 
992 void QgsMeshParticleTracesField::addParticleXY( const QgsPointXY &startPoint, double lifeTime )
993 {
994  addParticle( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint(), lifeTime );
995 }
996 
997 void QgsMeshParticleTracesField::moveParticles()
998 {
999  stump();
1000  for ( auto &p : mParticles )
1001  {
1002  double spentTime = p.remainingTime; //adjust with the past remaining time
1003  size_t countAdded = 0;
1004  while ( spentTime < mTimeStep && p.lifeTime > 0 )
1005  {
1006  double timeToSpend = double( time( p.position ) );
1007  if ( timeToSpend > 0 )
1008  {
1009  p.lifeTime -= timeToSpend;
1010  spentTime += timeToSpend;
1011  QPoint dir = direction( p.position );
1012  if ( p.lifeTime > 0 )
1013  {
1014  p.position += dir;
1015  p.tail.emplace_back( p.position );
1016  countAdded++;
1017  }
1018  else
1019  {
1020  break;
1021  }
1022  }
1023  else
1024  {
1025  p.lifeTime = -1;
1026  break;
1027  }
1028  }
1029 
1030  if ( p.lifeTime <= 0 )
1031  {
1032  // the particle is not alive anymore
1033  p.lifeTime = 0;
1034  p.tail.clear();
1035  }
1036  else
1037  {
1038  p.remainingTime = spentTime - mTimeStep;
1039  while ( int( p.tail.size() ) > mMinTailLength && p.tail.size() > countAdded * mTailFactor )
1040  p.tail.erase( p.tail.begin() );
1041  drawParticleTrace( p );
1042  }
1043  }
1044 
1045  //remove empty (dead particles)
1046  int i = 0;
1047  while ( i < mParticles.count() )
1048  {
1049  if ( mParticles.at( i ).tail.size() == 0 )
1050  mParticles.removeAt( i );
1051  else
1052  ++i;
1053  }
1054 
1055  //add new particles if needed
1056  if ( mParticles.count() < mParticlesCount )
1057  addRandomParticles();
1058 }
1059 
1060 void QgsMeshParticleTracesField::addRandomParticles()
1061 {
1062  if ( !isValid() )
1063  return;
1064 
1065  if ( mParticlesCount < 0 ) //for tests, add one particle on the center of the map
1066  {
1067  addParticleXY( QgsPointXY( mMapToFieldPixel.xCenter(), mMapToFieldPixel.yCenter() ), mParticlesLifeTime );
1068  return;
1069  }
1070 
1071  int count = mParticlesCount - mParticles.count();
1072 
1073  for ( int i = 0; i < count; ++i )
1074  {
1075  int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) ) ;
1076  int yRandom = 1 + std::rand() / int ( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) ) ;
1077  double lifeTime = ( std::rand() / ( ( RAND_MAX + 1u ) / mParticlesLifeTime ) );
1078  addParticle( QPoint( xRandom, yRandom ), lifeTime );
1079  }
1080 }
1081 
1082 void QgsMeshParticleTracesField::storeInField( const QPair<QPoint, QgsMeshStreamField::FieldData> pixelData )
1083 {
1084  int i = pixelData.first.x();
1085  int j = pixelData.first.y();
1086  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1087  {
1088  mTimeField[j * mFieldSize.width() + i] = pixelData.second.time;
1089  int d = pixelData.second.directionX + 2 + ( pixelData.second.directionY + 1 ) * 3;
1090  mDirectionField[j * mFieldSize.width() + i] = static_cast<char>( d );
1091  mMagnitudeField[j * mFieldSize.width() + i] = pixelData.second.magnitude;
1092  }
1093 }
1094 
1095 void QgsMeshParticleTracesField::initField()
1096 {
1097  mTimeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), -1 );
1098  mDirectionField = QVector<char>( mFieldSize.width() * mFieldSize.height(), static_cast<char>( int( 0 ) ) );
1099  mMagnitudeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), 0 );
1100  initImage();
1101  mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
1102  mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) ); //alpha=0 -> no persitence, alpha=255 -> total persistence
1103 }
1104 
1105 bool QgsMeshParticleTracesField::isTraceExists( const QPoint &pixel ) const
1106 {
1107  int i = pixel.x();
1108  int j = pixel.y();
1109  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1110  {
1111  return mTimeField[j * mFieldSize.width() + i] >= 0;
1112  }
1113 
1114  return false;
1115 }
1116 
1117 void QgsMeshParticleTracesField::setStumpParticleWithLifeTime( bool stumpParticleWithLifeTime )
1118 {
1119  mStumpParticleWithLifeTime = stumpParticleWithLifeTime;
1120 }
1121 
1122 void QgsMeshParticleTracesField::setParticlesColor( const QColor &c )
1123 {
1124  mVectorColoring.setColor( c );
1125 }
1126 
1127 void QgsMeshParticleTracesField::setMinTailLength( int minTailLength )
1128 {
1129  mMinTailLength = minTailLength;
1130 }
1131 
1132 QgsMeshParticleTracesField &QgsMeshParticleTracesField::operator=( const QgsMeshParticleTracesField &other )
1133 {
1134  QgsMeshStreamField::operator=( other );
1135  mTimeField = other.mTimeField;
1136  mDirectionField = other.mDirectionField;
1137  mParticles = other.mParticles;
1138  mStumpImage = other.mStumpImage;
1139  mTimeStep = other.mTimeStep;
1140  mParticlesLifeTime = other.mParticlesLifeTime;
1141  mParticlesCount = other.mParticlesCount;
1142  mTailFactor = other.mTailFactor;
1143  mParticleColor = other.mParticleColor;
1144  mParticleSize = other.mParticleSize;
1145  mStumpFactor = other.mStumpFactor;
1146 
1147  return ( *this );
1148 }
1149 
1150 void QgsMeshParticleTracesField::setTailFactor( double tailFactor )
1151 {
1152  mTailFactor = tailFactor;
1153 }
1154 
1155 void QgsMeshParticleTracesField::setParticleSize( double particleSize )
1156 {
1157  mParticleSize = particleSize;
1158 }
1159 
1160 void QgsMeshParticleTracesField::setTimeStep( double timeStep )
1161 {
1162  mTimeStep = timeStep;
1163 }
1164 
1165 void QgsMeshParticleTracesField::setParticlesLifeTime( double particlesLifeTime )
1166 {
1167  mParticlesLifeTime = particlesLifeTime;
1168 }
1169 
1170 QImage QgsMeshParticleTracesField::imageRendered() const
1171 {
1172  return mTraceImage;
1173 }
1174 
1175 void QgsMeshParticleTracesField::stump()
1176 {
1177  QgsScopedQPainterState painterState( mPainter.get() );
1178  mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1179  mPainter->drawImage( QPoint( 0, 0 ), mStumpImage );
1180 }
1181 
1182 void QgsMeshParticleTracesField::setStumpFactor( int sf )
1183 {
1184  mStumpFactor = sf;
1185  mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
1186  mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) );
1187 }
1188 
1189 QPoint QgsMeshParticleTracesField::direction( QPoint position ) const
1190 {
1191  int i = position.x();
1192  int j = position.y();
1193  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1194  {
1195  int dir = static_cast<int>( mDirectionField[j * mFieldSize.width() + i] );
1196  if ( dir != 0 && dir < 10 )
1197  return QPoint( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 );
1198  }
1199  return QPoint( 0, 0 );
1200 }
1201 
1202 float QgsMeshParticleTracesField::time( QPoint position ) const
1203 {
1204  int i = position.x();
1205  int j = position.y();
1206  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1207  {
1208  return mTimeField[j * mFieldSize.width() + i];
1209  }
1210  return -1;
1211 }
1212 
1213 float QgsMeshParticleTracesField::magnitude( QPoint position ) const
1214 {
1215  int i = position.x();
1216  int j = position.y();
1217  if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1218  {
1219  return mMagnitudeField[j * mFieldSize.width() + i];
1220  }
1221  return -1;
1222 }
1223 
1224 void QgsMeshParticleTracesField::drawParticleTrace( const QgsMeshTraceParticle &particle )
1225 {
1226  const std::list<QPoint> &tail = particle.tail;
1227  if ( tail.size() == 0 )
1228  return;
1229  double iniWidth = mParticleSize;
1230  double finWidth = 0;
1231 
1232  size_t pixelCount = tail.size();
1233 
1234  double transparency = 1;
1235  if ( mStumpParticleWithLifeTime )
1236  transparency = sin( M_PI * particle.lifeTime / mParticlesLifeTime );
1237 
1238  double dw;
1239  if ( pixelCount > 1 )
1240  dw = ( iniWidth - finWidth ) / ( pixelCount );
1241  else
1242  dw = 0;
1243 
1244  auto ip1 = std::prev( tail.end() );
1245  auto ip2 = std::prev( ip1 );
1246  int i = 0;
1247  while ( ip1 != tail.begin() )
1248  {
1249  QPointF p1 = fieldToDevice( ( *ip1 ) );
1250  QPointF p2 = fieldToDevice( ( *ip2 ) );
1251  QColor traceColor = mVectorColoring.color( magnitude( *ip1 ) );
1252  traceColor.setAlphaF( traceColor.alphaF()*transparency );
1253  mPen.setColor( traceColor );
1254  mPen.setWidthF( iniWidth - i * dw );
1255  mPainter->setPen( mPen );
1256  mPainter->drawLine( p1, p2 );
1257  ip1--;
1258  ip2--;
1259  ++i;
1260  }
1261 }
1262 
1263 void QgsMeshParticleTracesField::setParticlesCount( int particlesCount )
1264 {
1265  mParticlesCount = particlesCount;
1266 }
1267 
1269  const QgsMeshDataBlock &dataSetVectorValues,
1270  const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1271  bool dataIsOnVertices,
1272  const QgsRenderContext &rendererContext,
1273  const QgsRectangle &layerExtent,
1274  double magMax,
1275  const QgsMeshRendererVectorSettings &vectorSettings ):
1276  mRendererContext( rendererContext )
1277 {
1278  mParticleField = std::unique_ptr<QgsMeshParticleTracesField>( new QgsMeshParticleTracesField( triangularMesh,
1279  dataSetVectorValues,
1280  scalarActiveFaceFlagValues,
1281  layerExtent,
1282  magMax,
1283  dataIsOnVertices,
1284  rendererContext,
1285  vectorSettings.vectorStrokeColoring() ) ) ;
1286  mParticleField->updateSize( rendererContext ) ;
1287 }
1288 
1290  mRendererContext( rendererContext )
1291 {
1292  if ( !layer->triangularMesh() )
1293  layer->reload();
1294 
1295  QgsMeshDataBlock vectorDatasetValues;
1296  QgsMeshDataBlock scalarActiveFaceFlagValues;
1297  bool vectorDataOnVertices;
1298  double magMax;
1299 
1300  QgsMeshDatasetIndex datasetIndex = layer->activeVectorDatasetAtTime( rendererContext.temporalRange() );
1301 
1302  // Find out if we can use cache up to date. If yes, use it and return
1303  int datasetGroupCount = layer->dataProvider()->datasetGroupCount();
1304  const QgsMeshRendererVectorSettings vectorSettings = layer->rendererSettings().vectorSettings( datasetIndex.group() );
1305  QgsMeshLayerRendererCache *cache = layer->rendererCache();
1306 
1307  if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) &&
1308  ( cache->mActiveVectorDatasetIndex == datasetIndex ) )
1309  {
1310  vectorDatasetValues = cache->mVectorDatasetValues;
1311  scalarActiveFaceFlagValues = cache->mScalarActiveFaceFlagValues;
1312  magMax = cache->mVectorDatasetMagMaximum;
1313  vectorDataOnVertices = cache->mVectorDataType == QgsMeshDatasetGroupMetadata::DataOnVertices;
1314  }
1315  else
1316  {
1317  const QgsMeshDatasetGroupMetadata metadata =
1318  layer->dataProvider()->datasetGroupMetadata( datasetIndex.group() );
1319  magMax = metadata.maximum();
1320  vectorDataOnVertices = metadata.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
1321 
1322  int count;
1323  if ( vectorDataOnVertices )
1324  count = layer->nativeMesh()->vertices.count();
1325  else
1326  count = layer->nativeMesh()->faces.count();
1327 
1328  vectorDatasetValues = QgsMeshLayerUtils::datasetValues( layer, datasetIndex, 0, count );
1329 
1330  scalarActiveFaceFlagValues = layer->dataProvider()->areFacesActive(
1331  datasetIndex,
1332  0,
1333  layer->nativeMesh()->faces.count() );
1334  }
1335 
1336  mParticleField = std::unique_ptr<QgsMeshParticleTracesField>( new QgsMeshParticleTracesField( ( *layer->triangularMesh() ),
1337  vectorDatasetValues,
1338  scalarActiveFaceFlagValues,
1339  layer->extent(),
1340  magMax,
1341  vectorDataOnVertices,
1342  rendererContext,
1343  vectorSettings.vectorStrokeColoring() ) ) ;
1344 
1345  mParticleField->setMinimizeFieldSize( false );
1346  mParticleField->updateSize( mRendererContext );
1347 }
1348 
1350  mRendererContext( other.mRendererContext ),
1351  mFPS( other.mFPS ),
1352  mVpixMax( other.mVpixMax ),
1353  mParticleLifeTime( other.mParticleLifeTime )
1354 {
1355  mParticleField = std::unique_ptr<QgsMeshParticleTracesField>(
1356  new QgsMeshParticleTracesField( *other.mParticleField ) );
1357 }
1358 
1359 
1361 {
1362  mParticleField->setParticlesCount( count );
1363  mParticleField->addRandomParticles();
1364 }
1365 
1367 {
1368  mParticleField->moveParticles();
1369  return mParticleField->image();
1370 }
1371 
1373 {
1374  if ( FPS > 0 )
1375  mFPS = FPS;
1376  else
1377  mFPS = 1;
1378 
1379  updateFieldParameter();
1380 }
1381 
1383 {
1384  mVpixMax = max;
1385  updateFieldParameter();
1386 }
1387 
1388 void QgsMeshVectorTraceAnimationGenerator::setParticlesLifeTime( double particleLifeTime )
1389 {
1390  mParticleLifeTime = particleLifeTime;
1391  updateFieldParameter();
1392 }
1393 
1395 {
1396  mParticleField->setParticlesColor( c );
1397 }
1398 
1400 {
1401  mParticleField->setParticleSize( width );
1402 }
1403 
1405 {
1406  mParticleField->setTailFactor( fct );
1407 }
1408 
1410 {
1411  mParticleField->setMinTailLength( l );
1412 }
1413 
1415 {
1416  if ( p < 0 )
1417  p = 0;
1418  if ( p > 1 )
1419  p = 1;
1420  mParticleField->setStumpFactor( int( 255 * p ) );
1421 }
1422 
1424 {
1425  mParticleField.reset( new QgsMeshParticleTracesField( *mParticleField ) );
1426  const_cast<QgsRenderContext &>( mRendererContext ) = other.mRendererContext;
1427  mFPS = other.mFPS;
1428  mVpixMax = other.mVpixMax;
1429  mParticleLifeTime = other.mParticleLifeTime;
1430 
1431  return ( *this );
1432 }
1433 
1434 void QgsMeshVectorTraceAnimationGenerator::updateFieldParameter()
1435 {
1436  double fieldTimeStep = mVpixMax / mFPS;
1437  double fieldLifeTime = mParticleLifeTime * mFPS * fieldTimeStep;
1438  mParticleField->setTimeStep( fieldTimeStep );
1439  mParticleField->setParticlesLifeTime( fieldLifeTime );
1440 }
1441 
1442 QgsMeshVectorTraceRenderer::QgsMeshVectorTraceRenderer(
1443  const QgsTriangularMesh &triangularMesh,
1444  const QgsMeshDataBlock &dataSetVectorValues,
1445  const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1446  bool dataIsOnVertices,
1447  const QgsMeshRendererVectorSettings &settings,
1448  QgsRenderContext &rendererContext,
1449  const QgsRectangle &layerExtent,
1450  double magMax ):
1451  mRendererContext( rendererContext )
1452 {
1453  mParticleField = std::unique_ptr<QgsMeshParticleTracesField>( new QgsMeshParticleTracesField( triangularMesh,
1454  dataSetVectorValues,
1455  scalarActiveFaceFlagValues,
1456  layerExtent,
1457  magMax,
1458  dataIsOnVertices,
1459  rendererContext,
1460  settings.vectorStrokeColoring() ) ) ;
1461  mParticleField->updateSize( rendererContext ) ;
1462 
1463  mParticleField->setParticleSize( rendererContext.convertToPainterUnits(
1464  settings.lineWidth(), QgsUnitTypes::RenderUnit::RenderMillimeters ) );
1465  mParticleField->setParticlesCount( settings.tracesSettings().particlesCount() );
1466  mParticleField->setTailFactor( 1 );
1467  mParticleField->setStumpParticleWithLifeTime( false );
1468  mParticleField->setTimeStep( rendererContext.convertToPainterUnits( settings.tracesSettings().maximumTailLength(),
1469  settings.tracesSettings().maximumTailLengthUnit() ) ); //as the particles go through 1 pix for dt=1 and Vmax
1470  mParticleField->addRandomParticles();
1471  mParticleField->moveParticles();
1472 }
1473 
1474 void QgsMeshVectorTraceRenderer::draw()
1475 {
1476  if ( mRendererContext.renderingStopped() )
1477  return;
1478  mRendererContext.painter()->drawImage( mParticleField->topLeft(), mParticleField->image() );
1479 }
1480 
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Class defining color to render mesh datasets.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
double mapRotation() const
Returns current map rotation in degrees (clockwise)
QgsMeshDataBlock is a block of integers/doubles that can be used to retrieve: active flags (e....
bool isValid() const
Whether the block is valid.
QgsMeshDatasetGroupMetadata is a collection of dataset group metadata such as whether the data is vec...
DataType dataType() const
Returns whether dataset group data is defined on vertices or faces or volumes.
double maximum() const
Returns maximum scalar value/vector magnitude present for whole dataset group.
@ DataOnVertices
Data is defined on vertices.
QgsMeshDatasetIndex is index that identifies the dataset group (e.g.
int group() const
Returns a group index.
virtual QgsMeshDatasetGroupMetadata datasetGroupMetadata(int groupIndex) const =0
Returns dataset group metadata.
virtual QgsMeshDataBlock areFacesActive(QgsMeshDatasetIndex index, int faceIndex, int count) const =0
Returns whether the faces are active for particular dataset.
virtual int datasetGroupCount() const =0
Returns number of datasets groups loaded.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:95
QgsRectangle extent() const override
Returns the extent of the layer.
QgsMeshRendererSettings rendererSettings() const
Returns renderer settings.
void reload() override
Synchronises with changes in the datasource.
QgsMesh * nativeMesh()
Returns native mesh (nullptr before rendering or calling to updateMesh)
QgsMeshDatasetIndex activeVectorDatasetAtTime(const QgsDateTimeRange &timeRange) const
Returns dataset index from active vector group depending on the time range If the temporal properties...
QgsMeshDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
QgsTriangularMesh * triangularMesh(double minimumTriangleSize=0) const
Returns triangular mesh (nullptr before rendering or calling to updateMesh).
QgsMeshLayerRendererCache * rendererCache()
Returns native mesh (nullptr before rendering)
QgsMeshRendererVectorSettings vectorSettings(int groupIndex) const
Returns renderer settings.
Represents a renderer settings for vector datasets.
int userGridCellWidth() const
Returns width in pixels of user grid cell.
QgsMeshRendererVectorTracesSettings tracesSettings() const
Returns settings for vector rendered with traces.
QColor color() const
Returns color used for drawing arrows.
int userGridCellHeight() const
Returns height in pixels of user grid cell.
double lineWidth() const
Returns line width of the arrow (in millimeters)
double filterMax() const
Returns filter value for vector magnitudes.
QgsInterpolatedLineColor vectorStrokeColoring() const
Returns the stroke coloring used to render vector datasets.
bool isOnUserDefinedGrid() const
Returns whether vectors are drawn on user-defined grid.
double filterMin() const
Returns filter value for vector magnitudes.
QgsMeshRendererVectorStreamlineSettings streamLinesSettings() const
Returns settings for vector rendered with streamlines.
SeedingStartPointsMethod seedingMethod() const
Returns the method used for seeding start points of strealines.
@ Random
Seeds start points randomly on the mesh.
@ MeshGridded
Seeds start points on the vertices mesh or user regular grid.
double seedingDensity() const
Returns the density used for seeding start points.
double maximumTailLength() const
Returns the maximum tail length.
QgsUnitTypes::RenderUnit maximumTailLengthUnit() const
Returns the maximum tail length unit.
int particlesCount() const
Returns particles count.
A wrapper for QgsMeshParticuleTracesField used to render the particles.
void setParticlesLifeTime(double particleLifeTime)
Sets maximum life time of particles in seconds.
void setMinimumTailLength(int l)
Sets the minimum tail length.
void setTailPersitence(double p)
Sets the visual persistence of the tail.
void setParticlesColor(const QColor &c)
Sets colors of particle.
QImage imageRendered()
Moves all the particles using frame per second (fps) to calculate the displacement and return the ren...
void setTailFactor(double fct)
Sets the tail factor, used to adjust the length of the tail. 0 : minimum length, >1 increase the tail...
void setFPS(int FPS)
Sets the number of frames per seconds that will be rendered.
QgsMeshVectorTraceAnimationGenerator(const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &dataSetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, bool dataIsOnVertices, const QgsRenderContext &rendererContext, const QgsRectangle &layerExtent, double magMax, const QgsMeshRendererVectorSettings &vectorSettings)
Constructor to use from QgsMeshVectorRenderer.
void setParticlesSize(double width)
Sets particle size in px.
void setMaxSpeedPixel(int max)
Sets the max number of pixels that can be go through by the particles in 1 second.
QgsMeshVectorTraceAnimationGenerator & operator=(const QgsMeshVectorTraceAnimationGenerator &other)
Assignment operator.
void seedRandomParticles(int count)
seeds particles in the vector fields
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
Contains information about the context of a rendering operation.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Scoped object for saving and restoring a QPainter object's state.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
Triangular/Derived Mesh is mesh with vertices in map coordinates.
const QVector< QgsMeshFace > & triangles() const
Returns triangles.
const QVector< QgsMeshVertex > & vertices() const
Returns vertices in map coordinate system.
QList< int > faceIndexesForRectangle(const QgsRectangle &rectangle) const
Finds indexes of triangles intersecting given bounding box It uses spatial indexing.
A class to represent a vector.
Definition: qgsvector.h:30
QgsVector rotateBy(double rot) const SIP_HOLDGIL
Rotates the vector by a specified angle.
Definition: qgsvector.cpp:21
double y() const SIP_HOLDGIL
Returns the vector's y-component.
Definition: qgsvector.h:156
double x() const SIP_HOLDGIL
Returns the vector's x-component.
Definition: qgsvector.h:147
double length() const SIP_HOLDGIL
Returns the length of the vector.
Definition: qgsvector.h:128
bool isInTriangleFace(const QgsPointXY point, const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Tests if point p is on the face defined with vertices.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
#define M_DEG2RAD
QVector< int > QgsMeshFace
List of vertex indexes.
QVector< QgsMeshVertex > vertices
QVector< QgsMeshFace > faces