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