QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
20#include "qgsrendercontext.h"
21
22#include <QPointer>
23
25
26#ifndef M_DEG2RAD
27#define M_DEG2RAD 0.0174532925
28#endif
29
30
31QgsVector 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
62QgsMeshVectorValueInterpolator &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
74QgsMeshVectorValueInterpolatorFromVertex::
75QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues )
76 : QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
77{
78
79}
80
81QgsMeshVectorValueInterpolatorFromVertex::
82QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh,
83 const QgsMeshDataBlock &datasetVectorValues,
84 const QgsMeshDataBlock &scalarActiveFaceFlagValues )
85 : QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
86{
87
88}
89
90QgsMeshVectorValueInterpolatorFromVertex::QgsMeshVectorValueInterpolatorFromVertex( const QgsMeshVectorValueInterpolatorFromVertex &other ):
91 QgsMeshVectorValueInterpolator( other )
92{}
93
94QgsMeshVectorValueInterpolatorFromVertex *QgsMeshVectorValueInterpolatorFromVertex::clone()
95{
96 return new QgsMeshVectorValueInterpolatorFromVertex( *this );
97}
98
99QgsMeshVectorValueInterpolatorFromVertex &QgsMeshVectorValueInterpolatorFromVertex::
100operator=( const QgsMeshVectorValueInterpolatorFromVertex &other )
101{
102 QgsMeshVectorValueInterpolator::operator=( other );
103 return ( *this );
104}
105
106QgsVector 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
133QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsTriangularMesh &triangularMesh,
134 const QgsMeshDataBlock &datasetVectorValues ):
135 mTriangularMesh( triangularMesh ),
136 mDatasetValues( datasetVectorValues ),
137 mUseScalarActiveFaceFlagValues( false )
138{}
139
140QgsMeshVectorValueInterpolator::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
149QgsMeshVectorValueInterpolator::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
158void 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
166bool QgsMeshVectorValueInterpolator::isVectorValid( const QgsVector &v ) const
167{
168 return !( std::isnan( v.x() ) || std::isnan( v.y() ) );
169
170}
171
172void 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
178QSize QgsMeshStreamField::size() const
179{
180 return mFieldSize;
181}
182
183QPoint QgsMeshStreamField::topLeft() const
184{
185 return mFieldTopLeftInDeviceCoordinates;
186}
187
188int QgsMeshStreamField::resolution() const
189{
190 return mFieldResolution;
191}
192
193QgsPointXY 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
201QgsMeshStreamField::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
238QgsMeshStreamField::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
263QgsMeshStreamField::~QgsMeshStreamField()
264{
265 if ( mPainter )
266 mPainter->end();
267}
268
269void 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
356void 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
365bool QgsMeshStreamField::isValid() const
366{
367 return mValid;
368}
369
370void QgsMeshStreamField::addTrace( QgsPointXY startPoint )
371{
372 addTrace( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint() );
373}
374
375
376void QgsMeshStreamField::addRandomTraces()
377{
378 if ( mMaximumMagnitude > 0 )
379 while ( mPixelFillingCount < mMaxPixelFillingCount && !mRenderContext.renderingStopped() )
380 addRandomTrace();
381}
382
383void 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
393void 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
408void 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
425void 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
617void QgsMeshStreamField::setResolution( int width )
618{
619 mFieldResolution = width;
620}
621
622QSize QgsMeshStreamField::imageSize() const
623{
624 return mFieldSize * mFieldResolution;
625}
626
627QPointF 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
634bool 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
647void QgsMeshStreamlinesField::initField()
648{
649 mField = QVector<bool>( mFieldSize.width() * mFieldSize.height(), false );
650 initImage();
651}
652
653QgsMeshStreamlinesField::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
671QgsMeshStreamlinesField::QgsMeshStreamlinesField( const QgsMeshStreamlinesField &other ):
672 QgsMeshStreamField( other ),
673 mField( other.mField )
674{}
675
676QgsMeshStreamlinesField &QgsMeshStreamlinesField::operator=( const QgsMeshStreamlinesField &other )
677{
678 QgsMeshStreamField::operator=( other );
679 mField = other.mField;
680 return *this;
681}
682
683void 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
693void 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
704void 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
726void 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
732void 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
758bool 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
770bool 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
781void QgsMeshStreamField::setMinimizeFieldSize( bool minimizeFieldSize )
782{
783 mMinimizeFieldSize = minimizeFieldSize;
784}
785
786QgsMeshStreamField &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
813void 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
824bool QgsMeshStreamField::filterMag( double value ) const
825{
826 return ( mMinMagFilter < 0 || value > mMinMagFilter ) && ( mMaxMagFilter < 0 || value < mMaxMagFilter );
827}
828
829QImage QgsMeshStreamField::image()
830{
831 return mTraceImage.scaled( mFieldSize * mFieldResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
832}
833
834void QgsMeshStreamField::setPixelFillingDensity( double maxFilling )
835{
836 mPixelFillingDensity = maxFilling;
837 mMaxPixelFillingCount = int( mPixelFillingDensity * mFieldSize.width() * mFieldSize.height() );
838}
839
840void QgsMeshStreamField::setColor( QColor color )
841{
842 mPen.setColor( color );
843}
844
845void QgsMeshStreamField::setLineWidth( double width )
846{
847 mPen.setWidthF( width );
848}
849
850void QgsMeshStreamField::setFilter( double min, double max )
851{
852 mMinMagFilter = min;
853 mMaxMagFilter = max;
854}
855
856QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues ):
857 QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
858{}
859
860QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
861 QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
862{}
863
864QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsMeshVectorValueInterpolatorFromFace &other ):
865 QgsMeshVectorValueInterpolator( other )
866{}
867
868QgsMeshVectorValueInterpolatorFromFace *QgsMeshVectorValueInterpolatorFromFace::clone()
869{
870 return new QgsMeshVectorValueInterpolatorFromFace( *this );
871}
872
873QgsMeshVectorValueInterpolatorFromFace &QgsMeshVectorValueInterpolatorFromFace::operator=( const QgsMeshVectorValueInterpolatorFromFace &other )
874{
875 QgsMeshVectorValueInterpolator::operator=( other );
876 return ( *this );
877}
878
879QgsVector 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
898QgsMeshVectorStreamlineRenderer::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
938void QgsMeshVectorStreamlineRenderer::draw()
939{
940 if ( mRendererContext.renderingStopped() )
941 return;
942 mRendererContext.painter()->drawImage( mStreamlineField->topLeft(), mStreamlineField->image() );
943}
944
945QgsMeshParticleTracesField::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
966QgsMeshParticleTracesField::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
982void 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
995void QgsMeshParticleTracesField::addParticleXY( const QgsPointXY &startPoint, double lifeTime )
996{
997 addParticle( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint(), lifeTime );
998}
999
1000void 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
1063void 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
1085void 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
1098void 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
1108bool 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
1120void QgsMeshParticleTracesField::setStumpParticleWithLifeTime( bool stumpParticleWithLifeTime )
1121{
1122 mStumpParticleWithLifeTime = stumpParticleWithLifeTime;
1123}
1124
1125void QgsMeshParticleTracesField::setParticlesColor( const QColor &c )
1126{
1127 mVectorColoring.setColor( c );
1128}
1129
1130void QgsMeshParticleTracesField::setMinTailLength( int minTailLength )
1131{
1132 mMinTailLength = minTailLength;
1133}
1134
1135QgsMeshParticleTracesField &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
1153void QgsMeshParticleTracesField::setTailFactor( double tailFactor )
1154{
1155 mTailFactor = tailFactor;
1156}
1157
1158void QgsMeshParticleTracesField::setParticleSize( double particleSize )
1159{
1160 mParticleSize = particleSize;
1161}
1162
1163void QgsMeshParticleTracesField::setTimeStep( double timeStep )
1164{
1165 mTimeStep = timeStep;
1166}
1167
1168void QgsMeshParticleTracesField::setParticlesLifeTime( double particlesLifeTime )
1169{
1170 mParticlesLifeTime = particlesLifeTime;
1171}
1172
1173QImage QgsMeshParticleTracesField::imageRendered() const
1174{
1175 return mTraceImage;
1176}
1177
1178void QgsMeshParticleTracesField::stump()
1179{
1180 QgsScopedQPainterState painterState( mPainter.get() );
1181 mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1182 mPainter->drawImage( QPoint( 0, 0 ), mStumpImage );
1183}
1184
1185void 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
1192QPoint 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
1205float 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
1216float 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
1227void 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
1266void 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
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
1437void QgsMeshVectorTraceAnimationGenerator::updateFieldParameter()
1438{
1439 double fieldTimeStep = mVpixMax / mFPS;
1440 double fieldLifeTime = mParticleLifeTime * mFPS * fieldTimeStep;
1441 mParticleField->setTimeStep( fieldTimeStep );
1442 mParticleField->setParticlesLifeTime( fieldLifeTime );
1443}
1444
1445QgsMeshVectorTraceRenderer::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
1477void 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:100
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.
QgsMeshVectorTraceAnimationGenerator & operator=(const QgsMeshVectorTraceAnimationGenerator &other)
Assignment operator.
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.
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.
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.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
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:2527
#define M_DEG2RAD
QVector< int > QgsMeshFace
List of vertex indexes.
QVector< QgsMeshVertex > vertices
QVector< QgsMeshFace > faces