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