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