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