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