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