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