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