QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgspropertytransformer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspropertytransformer.cpp
3 --------------------------
4 Date : January 2017
5 Copyright : (C) 2017 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgslogger.h"
19#include "qgsexpression.h"
21#include "qgssymbollayerutils.h"
22#include "qgscolorramp.h"
23#include "qgscolorutils.h"
24#include "qgspointxy.h"
25
26
27//
28// QgsPropertyTransformer
29//
30
32{
33 QgsPropertyTransformer *transformer = nullptr;
34 switch ( type )
35 {
37 transformer = new QgsGenericNumericTransformer();
38 break;
40 transformer = new QgsSizeScaleTransformer();
41 break;
43 transformer = new QgsColorRampTransformer();
44 break;
45 }
46 return transformer;
47}
48
49QgsPropertyTransformer::QgsPropertyTransformer( double minValue, double maxValue )
50 : mMinValue( minValue )
51 , mMaxValue( maxValue )
52{}
53
55 : mMinValue( other.mMinValue )
56 , mMaxValue( other.mMaxValue )
57 , mCurveTransform( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr )
58{}
59
61{
62 mMinValue = other.mMinValue;
63 mMaxValue = other.mMaxValue;
64 mCurveTransform.reset( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr );
65 return *this;
66}
67
69
70bool QgsPropertyTransformer::loadVariant( const QVariant &transformer )
71{
72 QVariantMap transformerMap = transformer.toMap();
73
74 mMinValue = transformerMap.value( QStringLiteral( "minValue" ), 0.0 ).toDouble();
75 mMaxValue = transformerMap.value( QStringLiteral( "maxValue" ), 1.0 ).toDouble();
76 mCurveTransform.reset( nullptr );
77
78 QVariantMap curve = transformerMap.value( QStringLiteral( "curve" ) ).toMap();
79
80 if ( !curve.isEmpty() )
81 {
83 mCurveTransform->loadVariant( curve );
84 }
85
86 return true;
87}
88
90{
91 QVariantMap transformerMap;
92
93 transformerMap.insert( QStringLiteral( "minValue" ), mMinValue );
94 transformerMap.insert( QStringLiteral( "maxValue" ), mMaxValue );
95
96 if ( mCurveTransform )
97 {
98 transformerMap.insert( QStringLiteral( "curve" ), mCurveTransform->toVariant() );
99 }
100 return transformerMap;
101}
102
103QgsPropertyTransformer *QgsPropertyTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName )
104{
105 baseExpression.clear();
106 fieldName.clear();
107
108 if ( QgsPropertyTransformer *sizeScale = QgsSizeScaleTransformer::fromExpression( expression, baseExpression, fieldName ) )
109 return sizeScale;
110 else
111 return nullptr;
112}
113
115{
116 if ( !mCurveTransform )
117 return input;
118
120 return input;
121
122 // convert input into target range
123 double scaledInput = ( input - mMinValue ) / ( mMaxValue - mMinValue );
124
125 return mMinValue + ( mMaxValue - mMinValue ) * mCurveTransform->y( scaledInput );
126}
127
128
129//
130// QgsGenericNumericTransformer
131//
132
133QgsGenericNumericTransformer::QgsGenericNumericTransformer( double minValue, double maxValue, double minOutput, double maxOutput, double nullOutput, double exponent )
134 : QgsPropertyTransformer( minValue, maxValue )
135 , mMinOutput( minOutput )
136 , mMaxOutput( maxOutput )
137 , mNullOutput( nullOutput )
138 , mExponent( exponent )
139{}
140
142{
143 std::unique_ptr< QgsGenericNumericTransformer > t( new QgsGenericNumericTransformer( mMinValue,
144 mMaxValue,
145 mMinOutput,
146 mMaxOutput,
147 mNullOutput,
148 mExponent ) );
149 if ( mCurveTransform )
150 t->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
151 return t.release();
152}
153
155{
156 QVariantMap transformerMap = QgsPropertyTransformer::toVariant().toMap();
157
158 transformerMap.insert( QStringLiteral( "minOutput" ), mMinOutput );
159 transformerMap.insert( QStringLiteral( "maxOutput" ), mMaxOutput );
160 transformerMap.insert( QStringLiteral( "nullOutput" ), mNullOutput );
161 transformerMap.insert( QStringLiteral( "exponent" ), mExponent );
162
163 return transformerMap;
164}
165
166bool QgsGenericNumericTransformer::loadVariant( const QVariant &transformer )
167{
169
170 QVariantMap transformerMap = transformer.toMap();
171
172 mMinOutput = transformerMap.value( QStringLiteral( "minOutput" ), 0.0 ).toDouble();
173 mMaxOutput = transformerMap.value( QStringLiteral( "maxOutput" ), 1.0 ).toDouble();
174 mNullOutput = transformerMap.value( QStringLiteral( "nullOutput" ), 0.0 ).toDouble();
175 mExponent = transformerMap.value( QStringLiteral( "exponent" ), 1.0 ).toDouble();
176 return true;
177}
178
179double QgsGenericNumericTransformer::value( double input ) const
180{
182 return std::clamp( input, mMinOutput, mMaxOutput );
183
184 input = transformNumeric( input );
185 if ( qgsDoubleNear( mExponent, 1.0 ) )
186 return mMinOutput + ( std::clamp( input, mMinValue, mMaxValue ) - mMinValue ) * ( mMaxOutput - mMinOutput ) / ( mMaxValue - mMinValue );
187 else
188 return mMinOutput + std::pow( std::clamp( input, mMinValue, mMaxValue ) - mMinValue, mExponent ) * ( mMaxOutput - mMinOutput ) / std::pow( mMaxValue - mMinValue, mExponent );
189}
190
191QVariant QgsGenericNumericTransformer::transform( const QgsExpressionContext &context, const QVariant &v ) const
192{
193 Q_UNUSED( context )
194
195 if ( QgsVariantUtils::isNull( v ) )
196 return mNullOutput;
197
198 bool ok;
199 double dblValue = v.toDouble( &ok );
200
201 if ( ok )
202 {
203 //apply scaling to value
204 return value( dblValue );
205 }
206 else
207 {
208 return v;
209 }
210}
211
212QString QgsGenericNumericTransformer::toExpression( const QString &baseExpression ) const
213{
214 QString minValueString = QString::number( mMinValue );
215 QString maxValueString = QString::number( mMaxValue );
216 QString minOutputString = QString::number( mMinOutput );
217 QString maxOutputString = QString::number( mMaxOutput );
218 QString nullOutputString = QString::number( mNullOutput );
219 QString exponentString = QString::number( mExponent );
220
221 if ( qgsDoubleNear( mExponent, 1.0 ) )
222 return QStringLiteral( "coalesce(scale_linear(%1, %2, %3, %4, %5), %6)" ).arg( baseExpression, minValueString, maxValueString, minOutputString, maxOutputString, nullOutputString );
223 else
224 return QStringLiteral( "coalesce(scale_polynomial(%1, %2, %3, %4, %5, %6), %7)" ).arg( baseExpression, minValueString, maxValueString, minOutputString, maxOutputString, exponentString, nullOutputString );
225}
226
227QgsGenericNumericTransformer *QgsGenericNumericTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName )
228{
229 bool ok = false;
230
231 double nullValue = 0.0;
232 double exponent = 1.0;
233
234 baseExpression.clear();
235 fieldName.clear();
236
237 QgsExpression e( expression );
238
239 if ( !e.rootNode() )
240 return nullptr;
241
242 const QgsExpressionNodeFunction *f = dynamic_cast<const QgsExpressionNodeFunction *>( e.rootNode() );
243 if ( !f )
244 return nullptr;
245
246 QList<QgsExpressionNode *> args = f->args()->list();
247
248 // the scale function may be enclosed in a coalesce(expr, 0) to avoid NULL value
249 // to be drawn with the default size
250 if ( "coalesce" == QgsExpression::Functions()[f->fnIndex()]->name() )
251 {
252 f = dynamic_cast<const QgsExpressionNodeFunction *>( args[0] );
253 if ( !f )
254 return nullptr;
255 nullValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
256 if ( ! ok )
257 return nullptr;
258 args = f->args()->list();
259 }
260
261 if ( "scale_linear" == QgsExpression::Functions()[f->fnIndex()]->name() )
262 {
263 exponent = 1.0;
264 }
265 else if ( "scale_polynomial" == QgsExpression::Functions()[f->fnIndex()]->name() )
266 {
267 exponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
268 }
269 else
270 {
271 return nullptr;
272 }
273
274 bool expOk = true;
275 double minValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
276 expOk &= ok;
277 double maxValue = QgsExpression( args[2]->dump() ).evaluate().toDouble( &ok );
278 expOk &= ok;
279 double minOutput = QgsExpression( args[3]->dump() ).evaluate().toDouble( &ok );
280 expOk &= ok;
281 double maxOutput = QgsExpression( args[4]->dump() ).evaluate().toDouble( &ok );
282 expOk &= ok;
283
284 if ( !expOk )
285 {
286 return nullptr;
287 }
288
289 if ( args[0]->nodeType() == QgsExpressionNode::ntColumnRef )
290 {
291 fieldName = static_cast< QgsExpressionNodeColumnRef * >( args[0] )->name();
292 }
293 else
294 {
295 baseExpression = args[0]->dump();
296 }
297 return new QgsGenericNumericTransformer( minValue, maxValue, minOutput, maxOutput, nullValue, exponent );
298}
299
300
301
302//
303// QgsSizeScaleProperty
304//
305QgsSizeScaleTransformer::QgsSizeScaleTransformer( ScaleType type, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent )
306 : QgsPropertyTransformer( minValue, maxValue )
307 , mMinSize( minSize )
308 , mMaxSize( maxSize )
309 , mNullSize( nullSize )
310 , mExponent( exponent )
311{
312 setType( type );
313}
314
316{
317 std::unique_ptr< QgsSizeScaleTransformer > t( new QgsSizeScaleTransformer( mType,
318 mMinValue,
319 mMaxValue,
320 mMinSize,
321 mMaxSize,
322 mNullSize,
323 mExponent ) );
324 if ( mCurveTransform )
325 t->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
326 return t.release();
327}
328
330{
331 QVariantMap transformerMap = QgsPropertyTransformer::toVariant().toMap();
332
333 transformerMap.insert( QStringLiteral( "scaleType" ), static_cast< int >( mType ) );
334 transformerMap.insert( QStringLiteral( "minSize" ), mMinSize );
335 transformerMap.insert( QStringLiteral( "maxSize" ), mMaxSize );
336 transformerMap.insert( QStringLiteral( "nullSize" ), mNullSize );
337 transformerMap.insert( QStringLiteral( "exponent" ), mExponent );
338
339 return transformerMap;
340}
341
342bool QgsSizeScaleTransformer::loadVariant( const QVariant &transformer )
343{
345
346 QVariantMap transformerMap = transformer.toMap();
347
348 mType = static_cast< ScaleType >( transformerMap.value( QStringLiteral( "scaleType" ), Linear ).toInt() );
349 mMinSize = transformerMap.value( QStringLiteral( "minSize" ), 0.0 ).toDouble();
350 mMaxSize = transformerMap.value( QStringLiteral( "maxSize" ), 1.0 ).toDouble();
351 mNullSize = transformerMap.value( QStringLiteral( "nullSize" ), 0.0 ).toDouble();
352 mExponent = transformerMap.value( QStringLiteral( "exponent" ), 1.0 ).toDouble();
353
354 return true;
355}
356
357double QgsSizeScaleTransformer::size( double value ) const
358{
359 value = transformNumeric( value );
360
361 switch ( mType )
362 {
363 case Linear:
364 return mMinSize + ( std::clamp( value, mMinValue, mMaxValue ) - mMinValue ) * ( mMaxSize - mMinSize ) / ( mMaxValue - mMinValue );
365
366 case Area:
367 case Flannery:
368 case Exponential:
369 return mMinSize + std::pow( std::clamp( value, mMinValue, mMaxValue ) - mMinValue, mExponent ) * ( mMaxSize - mMinSize ) / std::pow( mMaxValue - mMinValue, mExponent );
370
371 }
372 return 0;
373}
374
376{
377 mType = type;
378 switch ( mType )
379 {
380 case Linear:
381 mExponent = 1.0;
382 break;
383 case Area:
384 mExponent = 0.5;
385 break;
386 case Flannery:
387 mExponent = 0.57;
388 break;
389 case Exponential:
390 //no change
391 break;
392 }
393}
394
395QVariant QgsSizeScaleTransformer::transform( const QgsExpressionContext &context, const QVariant &value ) const
396{
397 Q_UNUSED( context )
398
399 if ( QgsVariantUtils::isNull( value ) )
400 return mNullSize;
401
402 bool ok;
403 double dblValue = value.toDouble( &ok );
404
405 if ( ok )
406 {
407 //apply scaling to value
408 return size( dblValue );
409 }
410 else
411 {
412 return value;
413 }
414}
415
416QString QgsSizeScaleTransformer::toExpression( const QString &baseExpression ) const
417{
418 QString minValueString = QString::number( mMinValue );
419 QString maxValueString = QString::number( mMaxValue );
420 QString minSizeString = QString::number( mMinSize );
421 QString maxSizeString = QString::number( mMaxSize );
422 QString nullSizeString = QString::number( mNullSize );
423 QString exponentString = QString::number( mExponent );
424
425 switch ( mType )
426 {
427 case Linear:
428 return QStringLiteral( "coalesce(scale_linear(%1, %2, %3, %4, %5), %6)" ).arg( baseExpression, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );
429
430 case Area:
431 case Flannery:
432 case Exponential:
433 return QStringLiteral( "coalesce(scale_polynomial(%1, %2, %3, %4, %5, %6), %7)" ).arg( baseExpression, minValueString, maxValueString, minSizeString, maxSizeString, exponentString, nullSizeString );
434
435 }
436 return QString();
437}
438
439QgsSizeScaleTransformer *QgsSizeScaleTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName )
440{
441 bool ok = false;
442
444 double nullSize = 0.0;
445 double exponent = 1.0;
446
447 baseExpression.clear();
448 fieldName.clear();
449
450 QgsExpression e( expression );
451
452 if ( !e.rootNode() )
453 return nullptr;
454
455 const QgsExpressionNodeFunction *f = dynamic_cast<const QgsExpressionNodeFunction *>( e.rootNode() );
456 if ( !f )
457 return nullptr;
458
459 QList<QgsExpressionNode *> args = f->args()->list();
460
461 // the scale function may be enclosed in a coalesce(expr, 0) to avoid NULL value
462 // to be drawn with the default size
463 if ( "coalesce" == QgsExpression::Functions()[f->fnIndex()]->name() )
464 {
465 f = dynamic_cast<const QgsExpressionNodeFunction *>( args[0] );
466 if ( !f )
467 return nullptr;
468 nullSize = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
469 if ( ! ok )
470 return nullptr;
471 args = f->args()->list();
472 }
473
474 if ( "scale_linear" == QgsExpression::Functions()[f->fnIndex()]->name() )
475 {
476 type = Linear;
477 }
478 else if ( "scale_polynomial" == QgsExpression::Functions()[f->fnIndex()]->name() )
479 {
480 exponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
481 if ( ! ok )
482 return nullptr;
483 if ( qgsDoubleNear( exponent, 0.57, 0.001 ) )
484 type = Flannery;
485 else if ( qgsDoubleNear( exponent, 0.5, 0.001 ) )
486 type = Area;
487 else
489 }
490 else
491 {
492 return nullptr;
493 }
494
495 bool expOk = true;
496 double minValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
497 expOk &= ok;
498 double maxValue = QgsExpression( args[2]->dump() ).evaluate().toDouble( &ok );
499 expOk &= ok;
500 double minSize = QgsExpression( args[3]->dump() ).evaluate().toDouble( &ok );
501 expOk &= ok;
502 double maxSize = QgsExpression( args[4]->dump() ).evaluate().toDouble( &ok );
503 expOk &= ok;
504
505 if ( !expOk )
506 {
507 return nullptr;
508 }
509
510 if ( args[0]->nodeType() == QgsExpressionNode::ntColumnRef )
511 {
512 fieldName = static_cast< QgsExpressionNodeColumnRef * >( args[0] )->name();
513 }
514 else
515 {
516 baseExpression = args[0]->dump();
517 }
519}
520
521
522//
523// QgsColorRampTransformer
524//
525
526QgsColorRampTransformer::QgsColorRampTransformer( double minValue, double maxValue,
527 QgsColorRamp *ramp,
528 const QColor &nullColor,
529 const QString &rampName )
530 : QgsPropertyTransformer( minValue, maxValue )
531 , mGradientRamp( ramp )
532 , mNullColor( nullColor )
533 , mRampName( rampName )
534{
535
536}
537
539 : QgsPropertyTransformer( other )
540 , mGradientRamp( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr )
541 , mNullColor( other.mNullColor )
542 , mRampName( other.mRampName )
543{
544
545}
546
548{
550 mMinValue = other.mMinValue;
551 mMaxValue = other.mMaxValue;
552 mGradientRamp.reset( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr );
553 mNullColor = other.mNullColor;
554 mRampName = other.mRampName;
555 return *this;
556}
557
559{
560 std::unique_ptr< QgsColorRampTransformer > c( new QgsColorRampTransformer( mMinValue, mMaxValue,
561 mGradientRamp ? mGradientRamp->clone() : nullptr,
562 mNullColor ) );
563 c->setRampName( mRampName );
564 if ( mCurveTransform )
565 c->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
566 return c.release();
567}
568
570{
571 QVariantMap transformerMap = QgsPropertyTransformer::toVariant().toMap();
572
573 if ( mGradientRamp )
574 {
575 transformerMap.insert( QStringLiteral( "colorramp" ), QgsSymbolLayerUtils::colorRampToVariant( QStringLiteral( "[source]" ), mGradientRamp.get() ) );
576 }
577 transformerMap.insert( QStringLiteral( "nullColor" ), QgsColorUtils::colorToString( mNullColor ) );
578 transformerMap.insert( QStringLiteral( "rampName" ), mRampName );
579
580 return transformerMap;
581}
582
583bool QgsColorRampTransformer::loadVariant( const QVariant &definition )
584{
585 QVariantMap transformerMap = definition.toMap();
586
588
589 mGradientRamp.reset( nullptr );
590 if ( transformerMap.contains( QStringLiteral( "colorramp" ) ) )
591 {
592 setColorRamp( QgsSymbolLayerUtils::loadColorRamp( transformerMap.value( QStringLiteral( "colorramp" ) ).toMap() ) );
593 }
594
595 mNullColor = QgsColorUtils::colorFromString( transformerMap.value( QStringLiteral( "nullColor" ), QStringLiteral( "0,0,0,0" ) ).toString() );
596 mRampName = transformerMap.value( QStringLiteral( "rampName" ) ).toString();
597 return true;
598}
599
600QVariant QgsColorRampTransformer::transform( const QgsExpressionContext &context, const QVariant &value ) const
601{
602 Q_UNUSED( context )
603
604 if ( QgsVariantUtils::isNull( value ) )
605 return mNullColor;
606
607 bool ok;
608 double dblValue = value.toDouble( &ok );
609
610 if ( ok )
611 {
612 //apply scaling to value
613 return color( dblValue );
614 }
615 else
616 {
617 return value;
618 }
619}
620
621QString QgsColorRampTransformer::toExpression( const QString &baseExpression ) const
622{
623 if ( !mGradientRamp )
624 return QgsExpression::quotedValue( mNullColor.name() );
625
626 QString minValueString = QString::number( mMinValue );
627 QString maxValueString = QString::number( mMaxValue );
628 QString nullColorString = mNullColor.name();
629
630 return QStringLiteral( "coalesce(ramp_color('%1',scale_linear(%2, %3, %4, 0, 1)), '%5')" ).arg( !mRampName.isEmpty() ? mRampName : QStringLiteral( "custom ramp" ),
631 baseExpression, minValueString, maxValueString, nullColorString );
632}
633
634QColor QgsColorRampTransformer::color( double value ) const
635{
636 value = transformNumeric( value );
637 double scaledVal = std::clamp( ( value - mMinValue ) / ( mMaxValue - mMinValue ), 0.0, 1.0 );
638
639 if ( !mGradientRamp )
640 return mNullColor;
641
642 return mGradientRamp->color( scaledVal );
643}
644
646{
647 return mGradientRamp.get();
648}
649
651{
652 mGradientRamp.reset( ramp );
653}
654
655
656//
657// QgsCurveTransform
658//
659
660bool sortByX( const QgsPointXY &a, const QgsPointXY &b )
661{
662 return a.x() < b.x();
663}
664
666{
667 mControlPoints << QgsPointXY( 0, 0 ) << QgsPointXY( 1, 1 );
668 calcSecondDerivativeArray();
669}
670
671QgsCurveTransform::QgsCurveTransform( const QList<QgsPointXY> &controlPoints )
672 : mControlPoints( controlPoints )
673{
674 std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
675 calcSecondDerivativeArray();
676}
677
679{
680 delete [] mSecondDerivativeArray;
681}
682
684 : mControlPoints( other.mControlPoints )
685{
686 if ( other.mSecondDerivativeArray )
687 {
688 mSecondDerivativeArray = new double[ mControlPoints.count()];
689 memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
690 }
691}
692
694{
695 if ( this != &other )
696 {
697 mControlPoints = other.mControlPoints;
698 if ( other.mSecondDerivativeArray )
699 {
700 delete [] mSecondDerivativeArray;
701 mSecondDerivativeArray = new double[ mControlPoints.count()];
702 memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
703 }
704 }
705 return *this;
706}
707
708void QgsCurveTransform::setControlPoints( const QList<QgsPointXY> &points )
709{
710 mControlPoints = points;
711 std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
712 for ( int i = 0; i < mControlPoints.count(); ++i )
713 {
714 mControlPoints[ i ] = QgsPointXY( std::clamp( mControlPoints.at( i ).x(), 0.0, 1.0 ),
715 std::clamp( mControlPoints.at( i ).y(), 0.0, 1.0 ) );
716 }
717 calcSecondDerivativeArray();
718}
719
720void QgsCurveTransform::addControlPoint( double x, double y )
721{
722 QgsPointXY point( x, y );
723 if ( mControlPoints.contains( point ) )
724 return;
725
726 mControlPoints << point;
727 std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
728 calcSecondDerivativeArray();
729}
730
732{
733 for ( int i = 0; i < mControlPoints.count(); ++i )
734 {
735 if ( qgsDoubleNear( mControlPoints.at( i ).x(), x )
736 && qgsDoubleNear( mControlPoints.at( i ).y(), y ) )
737 {
738 mControlPoints.removeAt( i );
739 break;
740 }
741 }
742 calcSecondDerivativeArray();
743}
744
745// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
746// which in turn was adapted from
747// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
748
749double QgsCurveTransform::y( double x ) const
750{
751 int n = mControlPoints.count();
752 if ( n < 2 )
753 return std::clamp( x, 0.0, 1.0 ); // invalid
754 else if ( n < 3 )
755 {
756 // linear
757 if ( x <= mControlPoints.at( 0 ).x() )
758 return std::clamp( mControlPoints.at( 0 ).y(), 0.0, 1.0 );
759 else if ( x >= mControlPoints.at( n - 1 ).x() )
760 return std::clamp( mControlPoints.at( 1 ).y(), 0.0, 1.0 );
761 else
762 {
763 double dx = mControlPoints.at( 1 ).x() - mControlPoints.at( 0 ).x();
764 double dy = mControlPoints.at( 1 ).y() - mControlPoints.at( 0 ).y();
765 return std::clamp( ( x - mControlPoints.at( 0 ).x() ) * ( dy / dx ) + mControlPoints.at( 0 ).y(), 0.0, 1.0 );
766 }
767 }
768
769 // safety check
770 if ( x <= mControlPoints.at( 0 ).x() )
771 return std::clamp( mControlPoints.at( 0 ).y(), 0.0, 1.0 );
772 if ( x >= mControlPoints.at( n - 1 ).x() )
773 return std::clamp( mControlPoints.at( n - 1 ).y(), 0.0, 1.0 );
774
775 // find corresponding segment
776 QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
777 QgsPointXY currentControlPoint = *pointIt;
778 ++pointIt;
779 QgsPointXY nextControlPoint = *pointIt;
780
781 for ( int i = 0; i < n - 1; ++i )
782 {
783 if ( x < nextControlPoint.x() )
784 {
785 // found segment
786 double h = nextControlPoint.x() - currentControlPoint.x();
787 double t = ( x - currentControlPoint.x() ) / h;
788
789 double a = 1 - t;
790
791 return std::clamp( a * currentControlPoint.y() + t * nextControlPoint.y() + ( h * h / 6 ) * ( ( a * a * a - a ) * mSecondDerivativeArray[i] + ( t * t * t - t ) * mSecondDerivativeArray[i + 1] ),
792 0.0, 1.0 );
793 }
794
795 ++pointIt;
796 if ( pointIt == mControlPoints.constEnd() )
797 break;
798
799 currentControlPoint = nextControlPoint;
800 nextControlPoint = *pointIt;
801 }
802
803 //should not happen
804 return std::clamp( x, 0.0, 1.0 );
805}
806
807// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
808// which in turn was adapted from
809// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
810
811QVector<double> QgsCurveTransform::y( const QVector<double> &x ) const
812{
813 QVector<double> result;
814
815 int n = mControlPoints.count();
816 if ( n < 3 )
817 {
818 // invalid control points - use simple transform
819 const auto constX = x;
820 for ( double i : constX )
821 result << y( i );
822
823 return result;
824 }
825
826 // find corresponding segment
827 QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
828 QgsPointXY currentControlPoint = *pointIt;
829 ++pointIt;
830 QgsPointXY nextControlPoint = *pointIt;
831
832 int xIndex = 0;
833 double currentX = x.at( xIndex );
834 // safety check
835 while ( currentX <= currentControlPoint.x() )
836 {
837 result << std::clamp( currentControlPoint.y(), 0.0, 1.0 );
838 xIndex++;
839 currentX = x.at( xIndex );
840 }
841
842 for ( int i = 0; i < n - 1; ++i )
843 {
844 while ( currentX < nextControlPoint.x() )
845 {
846 // found segment
847 double h = nextControlPoint.x() - currentControlPoint.x();
848
849 double t = ( currentX - currentControlPoint.x() ) / h;
850
851 double a = 1 - t;
852
853 result << std::clamp( a * currentControlPoint.y() + t * nextControlPoint.y() + ( h * h / 6 ) * ( ( a * a * a - a )*mSecondDerivativeArray[i] + ( t * t * t - t )*mSecondDerivativeArray[i + 1] ), 0.0, 1.0 );
854 xIndex++;
855 if ( xIndex == x.count() )
856 return result;
857
858 currentX = x.at( xIndex );
859 }
860
861 ++pointIt;
862 if ( pointIt == mControlPoints.constEnd() )
863 break;
864
865 currentControlPoint = nextControlPoint;
866 nextControlPoint = *pointIt;
867 }
868
869 // safety check
870 while ( xIndex < x.count() )
871 {
872 result << std::clamp( nextControlPoint.y(), 0.0, 1.0 );
873 xIndex++;
874 }
875
876 return result;
877}
878
879bool QgsCurveTransform::readXml( const QDomElement &elem, const QDomDocument & )
880{
881 QString xString = elem.attribute( QStringLiteral( "x" ) );
882 QString yString = elem.attribute( QStringLiteral( "y" ) );
883
884 QStringList xVals = xString.split( ',' );
885 QStringList yVals = yString.split( ',' );
886 if ( xVals.count() != yVals.count() )
887 return false;
888
889 QList< QgsPointXY > newPoints;
890 bool ok = false;
891 for ( int i = 0; i < xVals.count(); ++i )
892 {
893 double x = xVals.at( i ).toDouble( &ok );
894 if ( !ok )
895 return false;
896 double y = yVals.at( i ).toDouble( &ok );
897 if ( !ok )
898 return false;
899 newPoints << QgsPointXY( x, y );
900 }
901 setControlPoints( newPoints );
902 return true;
903}
904
905bool QgsCurveTransform::writeXml( QDomElement &transformElem, QDomDocument & ) const
906{
907 QStringList x;
908 QStringList y;
909 const auto constMControlPoints = mControlPoints;
910 for ( const QgsPointXY &p : constMControlPoints )
911 {
912 x << qgsDoubleToString( p.x() );
913 y << qgsDoubleToString( p.y() );
914 }
915
916 transformElem.setAttribute( QStringLiteral( "x" ), x.join( ',' ) );
917 transformElem.setAttribute( QStringLiteral( "y" ), y.join( ',' ) );
918
919 return true;
920}
921
923{
924 QVariantMap transformMap;
925
926 QStringList x;
927 QStringList y;
928 const auto constMControlPoints = mControlPoints;
929 for ( const QgsPointXY &p : constMControlPoints )
930 {
931 x << qgsDoubleToString( p.x() );
932 y << qgsDoubleToString( p.y() );
933 }
934
935 transformMap.insert( QStringLiteral( "x" ), x.join( ',' ) );
936 transformMap.insert( QStringLiteral( "y" ), y.join( ',' ) );
937
938 return transformMap;
939}
940
941bool QgsCurveTransform::loadVariant( const QVariant &transformer )
942{
943 QVariantMap transformMap = transformer.toMap();
944
945 QString xString = transformMap.value( QStringLiteral( "x" ) ).toString();
946 QString yString = transformMap.value( QStringLiteral( "y" ) ).toString();
947
948 QStringList xVals = xString.split( ',' );
949 QStringList yVals = yString.split( ',' );
950 if ( xVals.count() != yVals.count() )
951 return false;
952
953 QList< QgsPointXY > newPoints;
954 bool ok = false;
955 for ( int i = 0; i < xVals.count(); ++i )
956 {
957 double x = xVals.at( i ).toDouble( &ok );
958 if ( !ok )
959 return false;
960 double y = yVals.at( i ).toDouble( &ok );
961 if ( !ok )
962 return false;
963 newPoints << QgsPointXY( x, y );
964 }
965 setControlPoints( newPoints );
966 return true;
967}
968
969// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
970// which in turn was adapted from
971// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
972
973void QgsCurveTransform::calcSecondDerivativeArray()
974{
975 int n = mControlPoints.count();
976 if ( n < 3 )
977 return; // cannot proceed
978
979 delete[] mSecondDerivativeArray;
980
981 double *matrix = new double[ n * 3 ];
982 double *result = new double[ n ];
983 matrix[0] = 0;
984 matrix[1] = 1;
985 matrix[2] = 0;
986 result[0] = 0;
987 QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
988 QgsPointXY pointIm1 = *pointIt;
989 ++pointIt;
990 QgsPointXY pointI = *pointIt;
991 ++pointIt;
992 QgsPointXY pointIp1 = *pointIt;
993
994 for ( int i = 1; i < n - 1; ++i )
995 {
996 matrix[i * 3 + 0 ] = ( pointI.x() - pointIm1.x() ) / 6.0;
997 matrix[i * 3 + 1 ] = ( pointIp1.x() - pointIm1.x() ) / 3.0;
998 matrix[i * 3 + 2 ] = ( pointIp1.x() - pointI.x() ) / 6.0;
999 result[i] = ( pointIp1.y() - pointI.y() ) / ( pointIp1.x() - pointI.x() ) - ( pointI.y() - pointIm1.y() ) / ( pointI.x() - pointIm1.x() );
1000
1001 // shuffle points
1002 pointIm1 = pointI;
1003 pointI = pointIp1;
1004 ++pointIt;
1005 if ( pointIt == mControlPoints.constEnd() )
1006 break;
1007
1008 pointIp1 = *pointIt;
1009 }
1010 matrix[( n - 1 ) * 3 + 0] = 0;
1011 matrix[( n - 1 ) * 3 + 1] = 1;
1012 matrix[( n - 1 ) * 3 + 2] = 0;
1013 result[n - 1] = 0;
1014
1015 // solving pass1 (up->down)
1016 for ( int i = 1; i < n; ++i )
1017 {
1018 double k = matrix[i * 3 + 0] / matrix[( i - 1 ) * 3 + 1];
1019 matrix[i * 3 + 1] -= k * matrix[( i - 1 ) * 3 + 2];
1020 matrix[i * 3 + 0] = 0;
1021 result[i] -= k * result[i - 1];
1022 }
1023 // solving pass2 (down->up)
1024 for ( int i = n - 2; i >= 0; --i )
1025 {
1026 double k = matrix[i * 3 + 2] / matrix[( i + 1 ) * 3 + 1];
1027 matrix[i * 3 + 1] -= k * matrix[( i + 1 ) * 3 + 0];
1028 matrix[i * 3 + 2] = 0;
1029 result[i] -= k * result[i + 1];
1030 }
1031
1032 // return second derivative value for each point
1033 mSecondDerivativeArray = new double[n];
1034 for ( int i = 0; i < n; ++i )
1035 {
1036 mSecondDerivativeArray[i] = result[i] / matrix[( i * 3 ) + 1];
1037 }
1038
1039 delete[] result;
1040 delete[] matrix;
1041}
1042
QgsPropertyTransformer subclass for transforming a numeric value into a color from a color ramp.
QgsColorRamp * colorRamp() const
Returns the color ramp used for calculating property colors.
QgsColorRampTransformer * clone() const override
Returns a clone of the transformer.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
QColor color(double value) const
Calculates the color corresponding to a specific value.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsColorRampTransformer & operator=(const QgsColorRampTransformer &other)
QgsColorRampTransformer(double minValue=0.0, double maxValue=1.0, QgsColorRamp *ramp=nullptr, const QColor &nullColor=QColor(0, 0, 0, 0), const QString &rampName=QString())
Constructor for QgsColorRampTransformer.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp to use for calculating property colors.
Abstract base class for color ramps.
Definition: qgscolorramp.h:29
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Handles scaling of input values to output values by using a curve created from smoothly joining a num...
QgsCurveTransform & operator=(const QgsCurveTransform &other)
bool readXml(const QDomElement &elem, const QDomDocument &doc)
Reads the curve's state from an XML element.
void setControlPoints(const QList< QgsPointXY > &points)
Sets the list of control points for the transform.
QVariant toVariant() const
Saves this curve transformer to a QVariantMap, wrapped in a QVariant.
bool writeXml(QDomElement &transformElem, QDomDocument &doc) const
Writes the current state of the transform into an XML element.
QgsCurveTransform()
Constructs a default QgsCurveTransform which linearly maps values between 0 and 1 unchanged.
void removeControlPoint(double x, double y)
Removes a control point from the transform.
void addControlPoint(double x, double y)
Adds a control point to the transform.
double y(double x) const
Returns the mapped y value corresponding to the specified x value.
bool loadVariant(const QVariant &transformer)
Load this curve transformer from a QVariantMap, wrapped in a QVariant.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
An expression node which takes it value from a feature's field.
QString dump() const override
Dump this node into a serialized (part) of an expression.
An expression node for expression functions.
int fnIndex() const
Returns the index of the node's function.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
Class for parsing and evaluation of expressions (formerly called "search strings").
static const QList< QgsExpressionFunction * > & Functions()
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required.
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
QVariant evaluate()
Evaluate the feature and return the result.
QgsPropertyTransformer subclass for scaling an input numeric value into an output numeric value.
double value(double input) const
Calculates the size corresponding to a specific input value.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
double exponent() const
Returns the exponent for an exponential expression.
QgsGenericNumericTransformer * clone() const override
Returns a clone of the transformer.
QgsGenericNumericTransformer(double minValue=0.0, double maxValue=1.0, double minOutput=0.0, double maxOutput=1.0, double nullOutput=0.0, double exponent=1.0)
Constructor for QgsGenericNumericTransformer.
static QgsGenericNumericTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
A class to represent a 2D point.
Definition: qgspointxy.h:60
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
Abstract base class for objects which transform the calculated value of a property.
static QgsPropertyTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding property transformer.
virtual bool loadVariant(const QVariant &transformer)
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsPropertyTransformer & operator=(const QgsPropertyTransformer &other)
virtual QVariant toVariant() const
Saves this transformer to a QVariantMap, wrapped in a QVariant.
double mMinValue
Minimum value expected by the transformer.
double maxValue() const
Returns the maximum value expected by the transformer.
std::unique_ptr< QgsCurveTransform > mCurveTransform
Optional curve transform.
QgsPropertyTransformer(double minValue=0.0, double maxValue=1.0)
Constructor for QgsPropertyTransformer.
virtual ~QgsPropertyTransformer()
@ GenericNumericTransformer
Generic transformer for numeric values (QgsGenericNumericTransformer)
@ SizeScaleTransformer
Size scaling transformer (QgsSizeScaleTransformer)
@ ColorRampTransformer
Color ramp transformer (QgsColorRampTransformer)
double transformNumeric(double input) const
Applies base class numeric transformations.
static QgsPropertyTransformer * create(Type type)
Factory method for creating a new property transformer of the specified type.
double minValue() const
Returns the minimum value expected by the transformer.
double mMaxValue
Maximum value expected by the transformer.
QgsPropertyTransformer subclass for scaling a value into a size according to various scaling methods.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsSizeScaleTransformer * clone() const override
Returns a clone of the transformer.
static QgsSizeScaleTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
ScaleType type() const
Returns the size transformer's scaling type (the method used to calculate the size from a value).
double maxSize() const
Returns the maximum calculated size.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
double nullSize() const
Returns the size value when an expression evaluates to NULL.
double minSize() const
Returns the minimum calculated size.
double size(double value) const
Calculates the size corresponding to a specific value.
ScaleType
Size scaling methods.
@ Exponential
Scale using set exponent.
@ Flannery
Flannery scaling method.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
void setType(ScaleType type)
Sets the size transformer's scaling type (the method used to calculate the size from a value).
double exponent() const
Returns the exponent for an exponential expression.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
QgsSizeScaleTransformer(ScaleType type=Linear, double minValue=0.0, double maxValue=1.0, double minSize=0.0, double maxSize=1.0, double nullSize=0.0, double exponent=1.0)
Constructor for QgsSizeScaleTransformer.
static QVariant colorRampToVariant(const QString &name, QgsColorRamp *ramp)
Saves a color ramp to a QVariantMap, wrapped in a QVariant.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
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
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:5124
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
bool sortByX(const QgsPointXY &a, const QgsPointXY &b)