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