QGIS API Documentation 4.3.0-Master (16649ce5cc6)
Loading...
Searching...
No Matches
qgsvectorfieldstreamfield.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorfieldstreamfield.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 <cmath>
21
22#include "qgsgeometry.h"
23#include "qgslinestring.h"
24#include "qgsrasterblock.h"
25#include "qgsrasterinterface.h"
26#include "qgsrastershader.h"
28
30
31#ifndef M_DEG2RAD
32#define M_DEG2RAD 0.0174532925
33#endif
34
36static QgsRectangle boundingBoxToScreenRectangle( const QgsMapToPixel &mtp, const QgsRectangle &bbox )
37{
38 const QgsPointXY topLeft = mtp.transform( bbox.xMinimum(), bbox.yMaximum() );
39 const QgsPointXY topRight = mtp.transform( bbox.xMaximum(), bbox.yMaximum() );
40 const QgsPointXY bottomLeft = mtp.transform( bbox.xMinimum(), bbox.yMinimum() );
41 const QgsPointXY bottomRight = mtp.transform( bbox.xMaximum(), bbox.yMinimum() );
42
43 const double xMin = std::min( { topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x() } );
44 const double xMax = std::max( { topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x() } );
45 const double yMin = std::min( { topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y() } );
46 const double yMax = std::max( { topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y() } );
47
48 return QgsRectangle( xMin, yMin, xMax, yMax );
49}
50
51QSize QgsVectorFieldStreamField::size() const
52{
53 return mFieldSize;
54}
55
56QPoint QgsVectorFieldStreamField::topLeft() const
57{
58 return mFieldTopLeftInDeviceCoordinates;
59}
60
61int QgsVectorFieldStreamField::resolution() const
62{
63 return mFieldResolution;
64}
65
66QgsPointXY QgsVectorFieldStreamField::positionToMapCoordinates( const QPoint &pixelPosition, const QgsPointXY &positionInPixel )
67{
68 QgsPointXY mapPoint = mMapToFieldPixel.toMapCoordinates( pixelPosition );
69 mapPoint = mapPoint + QgsVector( positionInPixel.x() * mMapToFieldPixel.mapUnitsPerPixel(), positionInPixel.y() * mMapToFieldPixel.mapUnitsPerPixel() );
70 return mapPoint;
71}
72
73QgsVectorFieldStreamField::QgsVectorFieldStreamField( std::unique_ptr<QgsVectorFieldValueSource> source, const QgsRenderContext &rendererContext, const QgsInterpolatedLineColor &vectorColoring, int resolution )
74 : mFieldResolution( resolution )
75 , mVectorColoring( vectorColoring )
76 , mRenderContext( rendererContext )
77 , mSource( std::move( source ) )
78{}
79
80QgsVectorFieldStreamField::QgsVectorFieldStreamField( const QgsVectorFieldStreamField &other )
81 : mFieldSize( other.mFieldSize )
82 , mFieldResolution( other.mFieldResolution )
83 , mPen( other.mPen )
84 , mTraceImage( other.mTraceImage )
85 , mMapToFieldPixel( other.mMapToFieldPixel )
86 , mOutputExtent( other.mOutputExtent )
87 , mVectorColoring( other.mVectorColoring )
88 , mDirectionField( other.mDirectionField )
89 , mRenderContext( other.mRenderContext )
90 , mPixelFillingCount( other.mPixelFillingCount )
91 , mMaxPixelFillingCount( other.mMaxPixelFillingCount )
92 , mMapExtent( other.mMapExtent )
93 , mFieldTopLeftInDeviceCoordinates( other.mFieldTopLeftInDeviceCoordinates )
94 , mValid( other.mValid )
95 , mPixelFillingDensity( other.mPixelFillingDensity )
96 , mMinMagFilter( other.mMinMagFilter )
97 , mMaxMagFilter( other.mMaxMagFilter )
98 , mMinimizeFieldSize( other.mMinimizeFieldSize )
99{
100 mPainter = std::make_unique<QPainter>( &mTraceImage );
101 mSource = other.mSource ? std::unique_ptr<QgsVectorFieldValueSource>( other.mSource->clone() ) : nullptr;
102}
103
104QgsVectorFieldStreamField::~QgsVectorFieldStreamField()
105{
106 if ( mPainter )
107 mPainter->end();
108}
109
110void QgsVectorFieldStreamField::updateSize( const QgsRenderContext &renderContext )
111{
112 mMapExtent = renderContext.mapExtent();
113 const QgsMapToPixel &deviceMapToPixel = renderContext.mapToPixel();
114 QgsRectangle layerExtent;
115 try
116 {
117 QgsCoordinateTransform extentTransform = renderContext.coordinateTransform();
118 extentTransform.setBallparkTransformsAreAppropriate( true );
119 layerExtent = extentTransform.transformBoundingBox( mSource->extent() );
120 }
121 catch ( QgsCsException &cse )
122 {
123 Q_UNUSED( cse )
124 //if the transform fails, consider the whole map
125 layerExtent = mMapExtent;
126 }
127
128 QgsRectangle interestZoneExtent;
129 if ( mMinimizeFieldSize )
130 interestZoneExtent = layerExtent.intersect( mMapExtent );
131 else
132 interestZoneExtent = mMapExtent;
133
134 if ( interestZoneExtent == QgsRectangle() )
135 {
136 mValid = false;
137 mFieldSize = QSize();
138 mFieldTopLeftInDeviceCoordinates = QPoint();
139 initField();
140 return;
141 }
142
143 QgsRectangle fieldInterestZoneInDeviceCoordinates = ::boundingBoxToScreenRectangle( deviceMapToPixel, interestZoneExtent );
144 mFieldTopLeftInDeviceCoordinates
145 = QPoint( static_cast<int>( std::round( fieldInterestZoneInDeviceCoordinates.xMinimum() ) ), static_cast<int>( std::round( fieldInterestZoneInDeviceCoordinates.yMinimum() ) ) );
146 int fieldWidthInDeviceCoordinate = int( fieldInterestZoneInDeviceCoordinates.width() );
147 int fieldHeightInDeviceCoordinate = int( fieldInterestZoneInDeviceCoordinates.height() );
148
149 int fieldWidth = int( fieldWidthInDeviceCoordinate / mFieldResolution );
150 int fieldHeight = int( fieldHeightInDeviceCoordinate / mFieldResolution );
151
152 //increase the field size if this size is not adjusted to extent of zone of interest in device coordinates
153 if ( fieldWidthInDeviceCoordinate % mFieldResolution > 0 )
154 fieldWidth++;
155 if ( fieldHeightInDeviceCoordinate % mFieldResolution > 0 )
156 fieldHeight++;
157
158 if ( fieldWidth == 0 || fieldHeight == 0 )
159 {
160 mFieldSize = QSize();
161 mOutputExtent = QgsRectangle();
162 }
163 else
164 {
165 mFieldSize.setWidth( fieldWidth );
166 mFieldSize.setHeight( fieldHeight );
167 QgsPointXY pt1 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates );
168 QgsPointXY pt2 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates + QPoint( fieldWidth, fieldHeight ) );
169 QgsPointXY pt3 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates + QPoint( 0, fieldHeight ) );
170 QgsPointXY pt4 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates + QPoint( fieldWidth, 0 ) );
171
172 mOutputExtent = QgsRectangle(
173 std::min( { pt1.x(), pt2.x(), pt3.x(), pt4.x() } ),
174 std::min( { pt1.y(), pt2.y(), pt3.y(), pt4.y() } ),
175 std::max( { pt1.x(), pt2.x(), pt3.x(), pt4.x() } ),
176 std::max( { pt1.y(), pt2.y(), pt3.y(), pt4.y() } ),
177 true
178 );
179 }
180
181 double mapUnitPerFieldPixel;
182 if ( interestZoneExtent.width() > 0 )
183 mapUnitPerFieldPixel = deviceMapToPixel.mapUnitsPerPixel() * mFieldResolution * mFieldSize.width() / ( fieldWidthInDeviceCoordinate / static_cast<double>( mFieldResolution ) );
184 else
185 mapUnitPerFieldPixel = 1e-8;
186
187 int fieldRightDevice = mFieldTopLeftInDeviceCoordinates.x() + mFieldSize.width() * mFieldResolution;
188 int fieldBottomDevice = mFieldTopLeftInDeviceCoordinates.y() + mFieldSize.height() * mFieldResolution;
189 QgsPointXY fieldRightBottomMap = deviceMapToPixel.toMapCoordinates( fieldRightDevice, fieldBottomDevice );
190
191 int fieldTopDevice = mFieldTopLeftInDeviceCoordinates.x();
192 int fieldLeftDevice = mFieldTopLeftInDeviceCoordinates.y();
193 QgsPointXY fieldTopLeftMap = deviceMapToPixel.toMapCoordinates( fieldTopDevice, fieldLeftDevice );
194
195 double xc = ( fieldRightBottomMap.x() + fieldTopLeftMap.x() ) / 2;
196 double yc = ( fieldTopLeftMap.y() + fieldRightBottomMap.y() ) / 2;
197
198 mMapToFieldPixel = QgsMapToPixel( mapUnitPerFieldPixel, xc, yc, fieldWidth, fieldHeight, deviceMapToPixel.mapRotation() );
199
200 initField();
201 mValid = true;
202}
203
204void QgsVectorFieldStreamField::updateSize( const QgsRenderContext &renderContext, int resolution )
205{
206 if ( renderContext.mapExtent() == mMapExtent && resolution == mFieldResolution )
207 return;
208 mFieldResolution = resolution;
209
210 updateSize( renderContext );
211}
212
213bool QgsVectorFieldStreamField::isValid() const
214{
215 return mValid;
216}
217
218void QgsVectorFieldStreamField::addTrace( QgsPointXY startPoint )
219{
220 addTrace( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint() );
221}
222
223
224void QgsVectorFieldStreamField::addRandomTraces()
225{
226 if ( mSource && mSource->maximumMagnitude() > 0 )
227 while ( ( mPixelFillingCount < mMaxPixelFillingCount ) && ( !mRenderContext.feedback() || !mRenderContext.feedback()->isCanceled() || !mRenderContext.renderingStopped() ) )
228 addRandomTrace();
229}
230
231void QgsVectorFieldStreamField::addRandomTrace()
232{
233 if ( !mValid )
234 return;
235
236 int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) );
237 int yRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) );
238 addTrace( QPoint( xRandom, yRandom ) );
239}
240
241void QgsVectorFieldStreamField::addGriddedTraces( int dx, int dy )
242{
243 int i = 0;
244 while ( i < mFieldSize.width() && mRenderContext.feedback() && !mRenderContext.feedback()->isCanceled() )
245 {
246 int j = 0;
247 while ( j < mFieldSize.height() && mRenderContext.feedback() && !mRenderContext.feedback()->isCanceled() )
248 {
249 addTrace( QPoint( i, j ) );
250 j += dy;
251 }
252 i += dx;
253 }
254}
255
256void QgsVectorFieldStreamField::addTracesOnDataPoints( const QgsRectangle &extent )
257{
258 if ( !mSource )
259 return;
260
261 const QVector<QgsPointXY> points = mSource->seedPoints( extent );
262 for ( const QgsPointXY &point : points )
263 addTrace( point );
264}
265
266void QgsVectorFieldStreamField::addTrace( QPoint startPixel )
267{
268 //This is where each traces are constructed
269 if ( !mPainter )
270 return;
271
272 if ( isTraceExists( startPixel ) || isTraceOutside( startPixel ) )
273 return;
274
275 if ( !mSource )
276 return;
277
278 const double maximumMagnitude = mSource->maximumMagnitude();
279 if ( !( maximumMagnitude > 0 ) )
280 return;
281
282 mPainter->setPen( mPen );
283
284 //position in the pixelField
285 double x1 = 0;
286 double y1 = 0;
287
288 std::list<QPair<QPoint, FieldData>> chunkTrace;
289
290 QPoint currentPixel = startPixel;
291 QgsVector vector;
292 FieldData data;
293 data.time = 1;
294
295 while ( true )
296 {
297 QgsPointXY mapPosition = positionToMapCoordinates( currentPixel, QgsPointXY( x1, y1 ) );
298 vector = mSource->vectorValue( mapPosition );
299
300 if ( std::isnan( vector.x() ) || std::isnan( vector.y() ) )
301 {
302 mPixelFillingCount++;
303 setChunkTrace( chunkTrace );
304 break;
305 }
306
307 /* nondimensional value : Vu=2 when the particle need dt=1 to go through a pixel with the mMagMax magnitude
308 * The nondimensional size of the side of a pixel is 2
309 */
310 vector = vector.rotateBy( -mMapToFieldPixel.mapRotation() * M_DEG2RAD );
311 QgsVector vu = vector / maximumMagnitude * 2;
312 data.magnitude = vector.length();
313
314 double Vx = vu.x();
315 double Vy = vu.y();
316 double Vu = data.magnitude / maximumMagnitude * 2; //nondimensional vector magnitude
317
318 if ( qgsDoubleNear( Vu, 0 ) )
319 {
320 // no trace anymore
321 addPixelToChunkTrace( currentPixel, data, chunkTrace );
322 simplifyChunkTrace( chunkTrace );
323 setChunkTrace( chunkTrace );
324 break;
325 }
326
327 //calculates where the particle will be after dt=1,
328 QgsPointXY nextPosition = QgsPointXY( x1, y1 ) + vu;
329 int incX = 0;
330 int incY = 0;
331 if ( nextPosition.x() > 1 )
332 incX = +1;
333 if ( nextPosition.x() < -1 )
334 incX = -1;
335 if ( nextPosition.y() > 1 )
336 incY = +1;
337 if ( nextPosition.y() < -1 )
338 incY = -1;
339
340 if ( incX != 0 || incY != 0 )
341 {
342 data.directionX = incX;
343 data.directionY = -incY;
344 //the particule leave the current pixel --> store pixels, calculates where the particle is and change the current pixel
345 if ( chunkTrace.empty() )
346 {
347 storeInField( QPair<QPoint, FieldData>( currentPixel, data ) );
348 }
349 if ( addPixelToChunkTrace( currentPixel, data, chunkTrace ) )
350 {
351 setChunkTrace( chunkTrace );
352 clearChunkTrace( chunkTrace );
353 }
354
355 data.time = 1;
356 currentPixel += QPoint( incX, -incY );
357 x1 = nextPosition.x() - 2 * incX;
358 y1 = nextPosition.y() - 2 * incY;
359 }
360 else
361 {
362 double x2, y2;
363 /*the particule still in the pixel --> "push" the position with the vector value to join a border
364 * and calculate the time spent to go to this border
365 */
366 if ( qgsDoubleNear( Vy, 0 ) )
367 {
368 y2 = y1;
369 if ( Vx > 0 )
370 incX = +1;
371 else
372 incX = -1;
373
374 x2 = incX;
375 }
376 else if ( qgsDoubleNear( Vx, 0 ) )
377 {
378 x2 = x1;
379 if ( Vy > 0 )
380 incY = +1;
381 else
382 incY = -1;
383
384 y2 = incY;
385 }
386 else
387 {
388 if ( Vy > 0 )
389 x2 = x1 + ( 1 - y1 ) * Vx / fabs( Vy );
390 else
391 x2 = x1 + ( 1 + y1 ) * Vx / fabs( Vy );
392 if ( Vx > 0 )
393 y2 = y1 + ( 1 - x1 ) * Vy / fabs( Vx );
394 else
395 y2 = y1 + ( 1 + x1 ) * Vy / fabs( Vx );
396
397 if ( x2 >= 1 )
398 x2 = 1;
399
400 if ( x2 <= -1 )
401 x2 = -1;
402
403 if ( y2 >= 1 )
404 y2 = 1;
405
406 if ( y2 <= -1 )
407 y2 = -1;
408 }
409
410 //calculate distance
411 double dx = x2 - x1;
412 double dy = y2 - y1;
413 double dl = sqrt( dx * dx + dy * dy );
414
415 data.time += static_cast<float>( dl / Vu ); //adimensional time step : this the time needed to go to the border of the pixel
416 if ( data.time > 10000 ) //Guard to prevent that the particle never leave the pixel
417 {
418 addPixelToChunkTrace( currentPixel, data, chunkTrace );
419 setChunkTrace( chunkTrace );
420 break;
421 }
422 x1 = x2;
423 y1 = y2;
424 }
425
426 //test if the new current pixel is already defined, if yes no need to continue
427 if ( isTraceExists( currentPixel ) )
428 {
429 //Set the pixel in the chunk before adding the current pixel because this pixel is already defined
430 setChunkTrace( chunkTrace );
431 addPixelToChunkTrace( currentPixel, data, chunkTrace );
432 break;
433 }
434
435 if ( isTraceOutside( currentPixel ) )
436 {
437 setChunkTrace( chunkTrace );
438 break;
439 }
440
441 if ( mRenderContext.feedback() && mRenderContext.feedback()->isCanceled() )
442 break;
443
444 if ( mRenderContext.renderingStopped() )
445 break;
446 }
447
448 drawTrace( startPixel );
449}
450
451void QgsVectorFieldStreamField::setResolution( int width )
452{
453 mFieldResolution = width;
454}
455
456QSize QgsVectorFieldStreamField::imageSize() const
457{
458 return mFieldSize * mFieldResolution;
459}
460
461QPointF QgsVectorFieldStreamField::fieldToDevice( const QPoint &pixel ) const
462{
463 QPointF p( pixel );
464 p = mFieldResolution * p + QPointF( mFieldResolution - 1, mFieldResolution - 1 ) / 2;
465 return p;
466}
467
468bool QgsVectorFieldStreamField::addPixelToChunkTrace( QPoint &pixel, QgsVectorFieldStreamField::FieldData &data, std::list<QPair<QPoint, QgsVectorFieldStreamField::FieldData> > &chunkTrace )
469{
470 chunkTrace.emplace_back( pixel, data );
471 if ( chunkTrace.size() == 3 )
472 {
473 simplifyChunkTrace( chunkTrace );
474 return true;
475 }
476 return false;
477}
478
479void QgsVectorFieldStreamlinesField::initField()
480{
481 mField = QVector<bool>( mFieldSize.width() * mFieldSize.height(), false );
482 mDirectionField = QVector<unsigned char>( mFieldSize.width() * mFieldSize.height(), static_cast<unsigned char>( int( 0 ) ) );
483 initImage();
484}
485
486void QgsVectorFieldStreamlinesField::initImage()
487{
488 mTraceImage = QImage();
489 switch ( mVectorColoring.coloringMethod() )
490 {
492 {
493 QSize imgSize = mFieldSize * mFieldResolution;
494 QgsRenderContext fieldContext = mRenderContext;
495
496 fieldContext.setMapToPixel( mMapToFieldPixel );
497 // the returned interface keeps a reference on fieldContext, so it must not outlive this scope
498 std::unique_ptr<QgsRasterInterface> magnitudeSource = mSource ? mSource->magnitudeSource( fieldContext, imgSize ) : nullptr;
499
500 if ( magnitudeSource && imgSize.isValid() )
501 {
503 sh->setRasterShaderFunction( new QgsColorRampShader( mVectorColoring.colorRampShader() ) ); // takes ownership of fcn
504 QgsSingleBandPseudoColorRenderer renderer( magnitudeSource.get(), 0, sh ); // takes ownership of sh
505 std::unique_ptr<QgsRasterBlock> bl( renderer.block( 0, mOutputExtent, imgSize.width(), imgSize.height(), mFeedBack ) );
506 mTraceImage = bl->image();
507 }
508 else
509 {
510 // the source cannot provide a magnitude raster, degrade to a flat single color
511 mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32_Premultiplied );
512 if ( !mTraceImage.isNull() )
513 mTraceImage.fill( mVectorColoring.singleColor() );
514 }
515 }
516 break;
518 {
519 mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32_Premultiplied );
520 QColor col = mVectorColoring.singleColor();
521 mTraceImage.fill( col );
522 }
523 break;
524 }
525
526 if ( !mTraceImage.isNull() )
527 {
528 mPainter = std::make_unique<QPainter>( &mTraceImage );
529 mPainter->setRenderHint( QPainter::Antialiasing, true );
530
531 mDrawingTraceImage = QImage( mTraceImage.size(), QImage::Format_ARGB32_Premultiplied );
532 mDrawingTraceImage.fill( Qt::transparent );
533 mDrawingTracePainter = std::make_unique<QPainter>( &mDrawingTraceImage );
534 mDrawingTracePainter->setRenderHint( QPainter::Antialiasing, true );
535 }
536}
537
538void QgsVectorFieldStreamField::clearChunkTrace( std::list<QPair<QPoint, QgsVectorFieldStreamField::FieldData> > &chunkTrace )
539{
540 auto one_before_end = std::prev( chunkTrace.end() );
541 chunkTrace.erase( chunkTrace.begin(), one_before_end );
542}
543
544void QgsVectorFieldStreamField::simplifyChunkTrace( std::list<QPair<QPoint, FieldData> > &chunkTrace )
545{
546 if ( chunkTrace.size() != 3 )
547 return;
548
549 auto ip3 = chunkTrace.begin();
550 auto ip1 = ip3++;
551 auto ip2 = ip3++;
552
553 while ( ip3 != chunkTrace.end() && ip2 != chunkTrace.end() )
554 {
555 QPoint v1 = ( *ip1 ).first - ( *ip2 ).first;
556 QPoint v2 = ( *ip2 ).first - ( *ip3 ).first;
557 if ( v1.x() * v2.x() + v1.y() * v2.y() == 0 )
558 {
559 ( *ip1 ).second.time += ( ( *ip2 ).second.time ) / 2;
560 ( *ip3 ).second.time += ( ( *ip2 ).second.time ) / 2;
561 ( *ip1 ).second.directionX += ( *ip2 ).second.directionX;
562 ( *ip1 ).second.directionY += ( *ip2 ).second.directionY;
563 chunkTrace.erase( ip2 );
564 }
565 ip1 = ip3++;
566 ip2 = ip3++;
567 }
568}
569
570QgsVectorFieldStreamlinesField::QgsVectorFieldStreamlinesField(
571 std::unique_ptr<QgsVectorFieldValueSource> source, QgsRenderContext &rendererContext, const QgsInterpolatedLineColor &vectorColoring, QgsRasterBlockFeedback *feedBack
572)
573 : QgsVectorFieldStreamField( std::move( source ), rendererContext, vectorColoring )
574 , mFeedBack( feedBack )
575{}
576
577void QgsVectorFieldStreamlinesField::compose()
578{
579 if ( !mPainter )
580 return;
581 mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
582 mPainter->drawImage( 0, 0, mDrawingTraceImage );
583}
584
585void QgsVectorFieldStreamlinesField::storeInField( const QPair<QPoint, FieldData> pixelData )
586{
587 int i = pixelData.first.x();
588 int j = pixelData.first.y();
589 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
590 {
591 mField[j * mFieldSize.width() + i] = true;
592 int d = pixelData.second.directionX + 2 + ( pixelData.second.directionY + 1 ) * 3;
593 mDirectionField[j * mFieldSize.width() + i] = static_cast<unsigned char>( d );
594 }
595}
596
597void QgsVectorFieldStreamField::setChunkTrace( std::list<QPair<QPoint, FieldData> > &chunkTrace )
598{
599 auto p = chunkTrace.begin();
600 while ( p != chunkTrace.end() )
601 {
602 storeInField( ( *p ) );
603 mPixelFillingCount++;
604 ++p;
605 }
606}
607
608void QgsVectorFieldStreamlinesField::drawTrace( const QPoint &start ) const
609{
610 if ( !isTraceExists( start ) || isTraceOutside( start ) )
611 return;
612
613 if ( !mDrawingTracePainter )
614 return;
615
616 QPoint pt1 = start;
617 QPoint curPt = pt1;
618 int fieldWidth = mFieldSize.width();
619 QSet<QgsPointXY> path;
620 unsigned char dir = 0;
621 unsigned char prevDir = mDirectionField.at( pt1.y() * fieldWidth + pt1.x() );
622
623 QVector<double> xPoly;
624 QVector<double> yPoly;
625 QPointF devicePt = fieldToDevice( pt1 );
626 xPoly.append( devicePt.x() );
627 yPoly.append( devicePt.y() );
628
629 while ( isTraceExists( curPt ) && !isTraceOutside( curPt ) && !path.contains( curPt ) )
630 {
631 dir = mDirectionField.at( curPt.y() * fieldWidth + curPt.x() );
632 if ( dir == 5 ) //no direction, static pixel
633 break;
634
635 const QPoint curPtDir( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 );
636 const QPoint pt2 = curPt + curPtDir;
637
638 if ( dir != prevDir )
639 {
640 path.insert( curPt );
641 devicePt = fieldToDevice( curPt );
642 xPoly.append( devicePt.x() );
643 yPoly.append( devicePt.y() );
644 prevDir = dir;
645 }
646 curPt = pt2;
647 }
648
649 if ( !isTraceExists( curPt ) || isTraceOutside( curPt ) )
650 {
651 // just add the last point
652 devicePt = fieldToDevice( curPt - QPoint( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 ) );
653 xPoly.append( devicePt.x() );
654 yPoly.append( devicePt.y() );
655 }
656
657 QgsGeometry geom( new QgsLineString( xPoly, yPoly ) );
658 geom = geom.simplify( 1.5 * mFieldResolution ).smooth( 1, 0.25, -1.0, 45 );
659 QPen pen = mPen;
660 pen.setColor( QColor( 0, 0, 0, 255 ) );
661 mDrawingTracePainter->setPen( pen );
662 mDrawingTracePainter->drawPolyline( geom.asQPolygonF() );
663}
664
665bool QgsVectorFieldStreamlinesField::isTraceExists( const QPoint &pixel ) const
666{
667 int i = pixel.x();
668 int j = pixel.y();
669 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
670 {
671 return mField[j * mFieldSize.width() + i];
672 }
673
674 return false;
675}
676
677bool QgsVectorFieldStreamField::isTraceOutside( const QPoint &pixel ) const
678{
679 int i = pixel.x();
680 int j = pixel.y();
681
682 return !( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() );
683}
684
685void QgsVectorFieldStreamField::setMinimizeFieldSize( bool minimizeFieldSize )
686{
687 mMinimizeFieldSize = minimizeFieldSize;
688}
689
690QgsVectorFieldStreamField &QgsVectorFieldStreamField::operator=( const QgsVectorFieldStreamField &other )
691{
692 if ( &other == this )
693 return *this;
694
695 mFieldSize = other.mFieldSize;
696 mFieldResolution = other.mFieldResolution;
697 mPen = other.mPen;
698 mTraceImage = other.mTraceImage;
699 mMapToFieldPixel = other.mMapToFieldPixel;
700 mOutputExtent = other.mOutputExtent;
701 mVectorColoring = other.mVectorColoring;
702 mDirectionField = other.mDirectionField;
703 mRenderContext = other.mRenderContext;
704 mPixelFillingCount = other.mPixelFillingCount;
705 mMaxPixelFillingCount = other.mMaxPixelFillingCount;
706 mMapExtent = other.mMapExtent;
707 mFieldTopLeftInDeviceCoordinates = other.mFieldTopLeftInDeviceCoordinates;
708 mValid = other.mValid;
709 mPixelFillingDensity = other.mPixelFillingDensity;
710 mMinMagFilter = other.mMinMagFilter;
711 mMaxMagFilter = other.mMaxMagFilter;
712 mMinimizeFieldSize = other.mMinimizeFieldSize;
713 mSource = other.mSource ? std::unique_ptr<QgsVectorFieldValueSource>( other.mSource->clone() ) : nullptr;
714
715 mPainter = std::make_unique<QPainter>( &mTraceImage );
716
717 return ( *this );
718}
719
720void QgsVectorFieldStreamField::initImage()
721{
722 mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
723 if ( !mTraceImage.isNull() )
724 {
725 mTraceImage.fill( 0X00000000 );
726 mPainter = std::make_unique<QPainter>( &mTraceImage );
727 mPainter->setRenderHint( QPainter::Antialiasing, true );
728 mPainter->setPen( mPen );
729 }
730}
731
732bool QgsVectorFieldStreamField::filterMag( double value ) const
733{
734 return ( mMinMagFilter < 0 || value > mMinMagFilter ) && ( mMaxMagFilter < 0 || value < mMaxMagFilter );
735}
736
737QImage QgsVectorFieldStreamField::image() const
738{
739 if ( mTraceImage.isNull() )
740 return QImage();
741 return mTraceImage.scaled( mFieldSize * mFieldResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
742}
743
744void QgsVectorFieldStreamField::setPixelFillingDensity( double maxFilling )
745{
746 mPixelFillingDensity = maxFilling;
747 mMaxPixelFillingCount = int( mPixelFillingDensity * mFieldSize.width() * mFieldSize.height() );
748}
749
750void QgsVectorFieldStreamField::setColor( QColor color )
751{
752 mPen.setColor( color );
753}
754
755void QgsVectorFieldStreamField::setLineWidth( double width )
756{
757 mPen.setWidthF( width );
758}
759
760void QgsVectorFieldStreamField::setFilter( double min, double max )
761{
762 mMinMagFilter = min;
763 mMaxMagFilter = max;
764}
765
766QgsVectorFieldParticleTracesField::QgsVectorFieldParticleTracesField( std::unique_ptr<QgsVectorFieldValueSource> source, const QgsRenderContext &rendererContext, const QgsInterpolatedLineColor &vectorColoring )
767 : QgsVectorFieldStreamField( std::move( source ), rendererContext, vectorColoring )
768{
769 std::srand( uint( ::time( nullptr ) ) );
770 mPen.setCapStyle( Qt::RoundCap );
771}
772
773QgsVectorFieldParticleTracesField::QgsVectorFieldParticleTracesField( const QgsVectorFieldParticleTracesField &other )
774 : QgsVectorFieldStreamField( other )
775 , mTimeField( other.mTimeField )
776 , mMagnitudeField( other.mMagnitudeField )
777 , mParticles( other.mParticles )
778 , mStumpImage( other.mStumpImage )
779 , mTimeStep( other.mTimeStep )
780 , mParticlesLifeTime( other.mParticlesLifeTime )
781 , mParticlesCount( other.mParticlesCount )
782 , mTailFactor( other.mTailFactor )
783 , mMinTailLength( other.mMinTailLength )
784 , mParticleColor( other.mParticleColor )
785 , mParticleSize( other.mParticleSize )
786 , mStumpFactor( other.mStumpFactor )
787 , mStumpParticleWithLifeTime( other.mStumpParticleWithLifeTime )
788{}
789
790void QgsVectorFieldParticleTracesField::addParticle( const QPoint &startPoint, double lifeTime )
791{
792 addTrace( startPoint );
793 if ( time( startPoint ) > 0 )
794 {
795 QgsVectorFieldTraceParticle p;
796 p.lifeTime = lifeTime;
797 p.position = startPoint;
798 mParticles.append( p );
799 }
800}
801
802void QgsVectorFieldParticleTracesField::addParticleXY( const QgsPointXY &startPoint, double lifeTime )
803{
804 addParticle( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint(), lifeTime );
805}
806
807void QgsVectorFieldParticleTracesField::moveParticles()
808{
809 stump();
810 for ( auto &p : mParticles )
811 {
812 double spentTime = p.remainingTime; //adjust with the past remaining time
813 size_t countAdded = 0;
814 while ( spentTime < mTimeStep && p.lifeTime > 0 )
815 {
816 double timeToSpend = double( time( p.position ) );
817 if ( timeToSpend > 0 )
818 {
819 p.lifeTime -= timeToSpend;
820 spentTime += timeToSpend;
821 QPoint dir = direction( p.position );
822 if ( p.lifeTime > 0 )
823 {
824 p.position += dir;
825 p.tail.emplace_back( p.position );
826 countAdded++;
827 }
828 else
829 {
830 break;
831 }
832 }
833 else
834 {
835 p.lifeTime = -1;
836 break;
837 }
838 }
839
840 if ( p.lifeTime <= 0 )
841 {
842 // the particle is not alive anymore
843 p.lifeTime = 0;
844 p.tail.clear();
845 }
846 else
847 {
848 p.remainingTime = spentTime - mTimeStep;
849 while ( static_cast<int>( p.tail.size() ) > mMinTailLength && static_cast<double>( p.tail.size() ) > ( static_cast<double>( countAdded ) * mTailFactor ) )
850 p.tail.erase( p.tail.begin() );
851 drawParticleTrace( p );
852 }
853 }
854
855 //remove empty (dead particles)
856 int i = 0;
857 while ( i < mParticles.count() )
858 {
859 if ( mParticles.at( i ).tail.size() == 0 )
860 mParticles.removeAt( i );
861 else
862 ++i;
863 }
864
865 //add new particles if needed
866 if ( mParticles.count() < mParticlesCount )
867 addRandomParticles();
868}
869
870void QgsVectorFieldParticleTracesField::addRandomParticles()
871{
872 if ( !isValid() )
873 return;
874
875 if ( mParticlesCount < 0 ) //for tests, add one particle on the center of the map
876 {
877 addParticleXY( QgsPointXY( mMapToFieldPixel.xCenter(), mMapToFieldPixel.yCenter() ), mParticlesLifeTime );
878 return;
879 }
880
881 int count = mParticlesCount - mParticles.count();
882
883 for ( int i = 0; i < count; ++i )
884 {
885 int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) );
886 int yRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) );
887 double lifeTime = ( std::rand() / ( ( RAND_MAX + 1u ) / mParticlesLifeTime ) );
888 addParticle( QPoint( xRandom, yRandom ), lifeTime );
889 }
890}
891
892void QgsVectorFieldParticleTracesField::storeInField( const QPair<QPoint, QgsVectorFieldStreamField::FieldData> pixelData )
893{
894 int i = pixelData.first.x();
895 int j = pixelData.first.y();
896 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
897 {
898 mTimeField[j * mFieldSize.width() + i] = pixelData.second.time;
899 int d = pixelData.second.directionX + 2 + ( pixelData.second.directionY + 1 ) * 3;
900 mDirectionField[j * mFieldSize.width() + i] = static_cast<unsigned char>( d );
901 mMagnitudeField[j * mFieldSize.width() + i] = static_cast<float>( pixelData.second.magnitude );
902 }
903}
904
905void QgsVectorFieldParticleTracesField::initField()
906{
907 mTimeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), -1 );
908 mDirectionField = QVector<unsigned char>( mFieldSize.width() * mFieldSize.height(), static_cast<unsigned char>( int( 0 ) ) );
909 mMagnitudeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), 0 );
910 initImage();
911 mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
912 mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) ); //alpha=0 -> no persitence, alpha=255 -> total persistence
913}
914
915bool QgsVectorFieldParticleTracesField::isTraceExists( const QPoint &pixel ) const
916{
917 int i = pixel.x();
918 int j = pixel.y();
919 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
920 {
921 return mTimeField[j * mFieldSize.width() + i] >= 0;
922 }
923
924 return false;
925}
926
927void QgsVectorFieldParticleTracesField::setStumpParticleWithLifeTime( bool stumpParticleWithLifeTime )
928{
929 mStumpParticleWithLifeTime = stumpParticleWithLifeTime;
930}
931
932void QgsVectorFieldParticleTracesField::setParticlesColor( const QColor &c )
933{
934 mVectorColoring.setColor( c );
935}
936
937QgsVectorFieldParticleTracesField &QgsVectorFieldParticleTracesField::operator=( const QgsVectorFieldParticleTracesField &other )
938{
939 if ( &other == this )
940 return *this;
941
942 QgsVectorFieldStreamField::operator=( other );
943 mTimeField = other.mTimeField;
944 mMagnitudeField = other.mMagnitudeField;
945 mDirectionField = other.mDirectionField;
946 mParticles = other.mParticles;
947 mStumpImage = other.mStumpImage;
948 mTimeStep = other.mTimeStep;
949 mParticlesLifeTime = other.mParticlesLifeTime;
950 mParticlesCount = other.mParticlesCount;
951 mMinTailLength = other.mMinTailLength;
952 mTailFactor = other.mTailFactor;
953 mParticleColor = other.mParticleColor;
954 mParticleSize = other.mParticleSize;
955 mStumpFactor = other.mStumpFactor;
956 mStumpParticleWithLifeTime = other.mStumpParticleWithLifeTime;
957
958 return ( *this );
959}
960
961void QgsVectorFieldParticleTracesField::setMinTailLength( int minTailLength )
962{
963 mMinTailLength = minTailLength;
964}
965
966void QgsVectorFieldParticleTracesField::setTailFactor( double tailFactor )
967{
968 mTailFactor = tailFactor;
969}
970
971void QgsVectorFieldParticleTracesField::setParticleSize( double particleSize )
972{
973 mParticleSize = particleSize;
974}
975
976void QgsVectorFieldParticleTracesField::setTimeStep( double timeStep )
977{
978 mTimeStep = timeStep;
979}
980
981void QgsVectorFieldParticleTracesField::setParticlesLifeTime( double particlesLifeTime )
982{
983 mParticlesLifeTime = particlesLifeTime;
984}
985
986QImage QgsVectorFieldParticleTracesField::imageRendered() const
987{
988 return mTraceImage;
989}
990
991void QgsVectorFieldParticleTracesField::stump()
992{
993 if ( !mPainter )
994 return;
995 QgsScopedQPainterState painterState( mPainter.get() );
996 mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
997 mPainter->drawImage( QPoint( 0, 0 ), mStumpImage );
998}
999
1000void QgsVectorFieldParticleTracesField::setStumpFactor( int sf )
1001{
1002 mStumpFactor = sf;
1003 mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
1004 mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) );
1005}
1006
1007QPoint QgsVectorFieldParticleTracesField::direction( QPoint position ) const
1008{
1009 int i = position.x();
1010 int j = position.y();
1011 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1012 {
1013 int dir = static_cast<int>( mDirectionField[j * mFieldSize.width() + i] );
1014 if ( dir != 0 && dir < 10 )
1015 return QPoint( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 );
1016 }
1017 return QPoint( 0, 0 );
1018}
1019
1020float QgsVectorFieldParticleTracesField::time( QPoint position ) const
1021{
1022 int i = position.x();
1023 int j = position.y();
1024 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1025 {
1026 return mTimeField[j * mFieldSize.width() + i];
1027 }
1028 return -1;
1029}
1030
1031float QgsVectorFieldParticleTracesField::magnitude( QPoint position ) const
1032{
1033 int i = position.x();
1034 int j = position.y();
1035 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1036 {
1037 return mMagnitudeField[j * mFieldSize.width() + i];
1038 }
1039 return -1;
1040}
1041
1042void QgsVectorFieldParticleTracesField::drawParticleTrace( const QgsVectorFieldTraceParticle &particle )
1043{
1044 if ( !mPainter )
1045 return;
1046 const std::list<QPoint> &tail = particle.tail;
1047 if ( tail.size() == 0 )
1048 return;
1049 double iniWidth = mParticleSize;
1050
1051 size_t pixelCount = tail.size();
1052
1053 double transparency = 1;
1054 if ( mStumpParticleWithLifeTime )
1055 transparency = sin( M_PI * particle.lifeTime / mParticlesLifeTime );
1056
1057 double dw;
1058 if ( pixelCount > 1 )
1059 dw = iniWidth / static_cast<double>( pixelCount );
1060 else
1061 dw = 0;
1062
1063 auto ip1 = std::prev( tail.end() );
1064 auto ip2 = std::prev( ip1 );
1065 int i = 0;
1066 while ( ip1 != tail.begin() )
1067 {
1068 QPointF p1 = fieldToDevice( ( *ip1 ) );
1069 QPointF p2 = fieldToDevice( ( *ip2 ) );
1070 QColor traceColor = mVectorColoring.color( magnitude( *ip1 ) );
1071 traceColor.setAlphaF( traceColor.alphaF() * transparency );
1072 mPen.setColor( traceColor );
1073 mPen.setWidthF( iniWidth - i * dw );
1074 mPainter->setPen( mPen );
1075 mPainter->drawLine( p1, p2 );
1076 ip1--;
1077 ip2--;
1078 ++i;
1079 }
1080}
1081
1082void QgsVectorFieldParticleTracesField::setParticlesCount( int particlesCount )
1083{
1084 mParticlesCount = particlesCount;
1085}
1086
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.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Feedback object tailored for raster block reading.
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
double xMaximum
double yMaximum
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Contains information about the context of a rendering operation.
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.
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double y() const
Returns the vector's y-component.
Definition qgsvector.h:155
QgsVector rotateBy(double rot) const
Rotates the vector by a specified angle.
Definition qgsvector.cpp:26
double x() const
Returns the vector's x-component.
Definition qgsvector.h:146
double length() const
Returns the length of the vector.
Definition qgsvector.h:127
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7693
#define M_DEG2RAD