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