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 
16 #include "qgspropertytransformer.h"
17 
18 #include "qgslogger.h"
19 #include "qgsexpression.h"
20 #include "qgsexpressionnodeimpl.h"
21 #include "qgssymbollayerutils.h"
22 #include "qgscolorramp.h"
23 #include "qgspointxy.h"
24 
25 
26 //
27 // QgsPropertyTransformer
28 //
29 
create(QgsPropertyTransformer::Type type)30 QgsPropertyTransformer *QgsPropertyTransformer::create( QgsPropertyTransformer::Type type )
31 {
32   QgsPropertyTransformer *transformer = nullptr;
33   switch ( type )
34   {
35     case GenericNumericTransformer:
36       transformer = new QgsGenericNumericTransformer();
37       break;
38     case SizeScaleTransformer:
39       transformer = new QgsSizeScaleTransformer();
40       break;
41     case ColorRampTransformer:
42       transformer = new QgsColorRampTransformer();
43       break;
44   }
45   return transformer;
46 }
47 
QgsPropertyTransformer(double minValue,double maxValue)48 QgsPropertyTransformer::QgsPropertyTransformer( double minValue, double maxValue )
49   : mMinValue( minValue )
50   , mMaxValue( maxValue )
51 {}
52 
QgsPropertyTransformer(const QgsPropertyTransformer & other)53 QgsPropertyTransformer::QgsPropertyTransformer( const QgsPropertyTransformer &other )
54   : mMinValue( other.mMinValue )
55   , mMaxValue( other.mMaxValue )
56   , mCurveTransform( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr )
57 {}
58 
operator =(const QgsPropertyTransformer & other)59 QgsPropertyTransformer &QgsPropertyTransformer::operator=( const QgsPropertyTransformer &other )
60 {
61   mMinValue = other.mMinValue;
62   mMaxValue = other.mMaxValue;
63   mCurveTransform.reset( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr );
64   return *this;
65 }
66 
67 QgsPropertyTransformer::~QgsPropertyTransformer() = default;
68 
loadVariant(const QVariant & transformer)69 bool 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   {
81     mCurveTransform.reset( new QgsCurveTransform() );
82     mCurveTransform->loadVariant( curve );
83   }
84 
85   return true;
86 }
87 
toVariant() const88 QVariant QgsPropertyTransformer::toVariant() const
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 
fromExpression(const QString & expression,QString & baseExpression,QString & fieldName)102 QgsPropertyTransformer *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 
transformNumeric(double input) const113 double QgsPropertyTransformer::transformNumeric( double input ) const
114 {
115   if ( !mCurveTransform )
116     return input;
117 
118   if ( qgsDoubleNear( mMaxValue, mMinValue ) )
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 
QgsGenericNumericTransformer(double minValue,double maxValue,double minOutput,double maxOutput,double nullOutput,double exponent)132 QgsGenericNumericTransformer::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 
clone() const140 QgsGenericNumericTransformer *QgsGenericNumericTransformer::clone() const
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 
toVariant() const153 QVariant QgsGenericNumericTransformer::toVariant() const
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 
loadVariant(const QVariant & transformer)165 bool QgsGenericNumericTransformer::loadVariant( const QVariant &transformer )
166 {
167   QgsPropertyTransformer::loadVariant( transformer );
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 
value(double input) const178 double QgsGenericNumericTransformer::value( double input ) const
179 {
180   if ( qgsDoubleNear( mMaxValue, mMinValue ) )
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 
transform(const QgsExpressionContext & context,const QVariant & v) const190 QVariant QgsGenericNumericTransformer::transform( const QgsExpressionContext &context, const QVariant &v ) const
191 {
192   Q_UNUSED( context )
193 
194   if ( v.isNull() )
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 
toExpression(const QString & baseExpression) const211 QString 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 
fromExpression(const QString & expression,QString & baseExpression,QString & fieldName)226 QgsGenericNumericTransformer *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 //
QgsSizeScaleTransformer(ScaleType type,double minValue,double maxValue,double minSize,double maxSize,double nullSize,double exponent)304 QgsSizeScaleTransformer::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 
clone() const314 QgsSizeScaleTransformer *QgsSizeScaleTransformer::clone() const
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 
toVariant() const328 QVariant QgsSizeScaleTransformer::toVariant() const
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 
loadVariant(const QVariant & transformer)341 bool QgsSizeScaleTransformer::loadVariant( const QVariant &transformer )
342 {
343   QgsPropertyTransformer::loadVariant( transformer );
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 
size(double value) const356 double 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 
setType(QgsSizeScaleTransformer::ScaleType type)374 void QgsSizeScaleTransformer::setType( QgsSizeScaleTransformer::ScaleType type )
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 
transform(const QgsExpressionContext & context,const QVariant & value) const394 QVariant QgsSizeScaleTransformer::transform( const QgsExpressionContext &context, const QVariant &value ) const
395 {
396   Q_UNUSED( context )
397 
398   if ( value.isNull() )
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 
toExpression(const QString & baseExpression) const415 QString 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 
fromExpression(const QString & expression,QString & baseExpression,QString & fieldName)438 QgsSizeScaleTransformer *QgsSizeScaleTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName )
439 {
440   bool ok = false;
441 
442   ScaleType type = Linear;
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
487       type = Exponential;
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   }
517   return new QgsSizeScaleTransformer( type, minValue, maxValue, minSize, maxSize, nullSize, exponent );
518 }
519 
520 
521 //
522 // QgsColorRampTransformer
523 //
524 
QgsColorRampTransformer(double minValue,double maxValue,QgsColorRamp * ramp,const QColor & nullColor)525 QgsColorRampTransformer::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 
QgsColorRampTransformer(const QgsColorRampTransformer & other)535 QgsColorRampTransformer::QgsColorRampTransformer( const QgsColorRampTransformer &other )
536   : QgsPropertyTransformer( other )
537   , mGradientRamp( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr )
538   , mNullColor( other.mNullColor )
539   , mRampName( other.mRampName )
540 {
541 
542 }
543 
operator =(const QgsColorRampTransformer & other)544 QgsColorRampTransformer &QgsColorRampTransformer::operator=( const QgsColorRampTransformer &other )
545 {
546   QgsPropertyTransformer::operator=( other );
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 
clone() const555 QgsColorRampTransformer *QgsColorRampTransformer::clone() const
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 
toVariant() const566 QVariant QgsColorRampTransformer::toVariant() const
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 
loadVariant(const QVariant & definition)580 bool QgsColorRampTransformer::loadVariant( const QVariant &definition )
581 {
582   QVariantMap transformerMap = definition.toMap();
583 
584   QgsPropertyTransformer::loadVariant( definition );
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 
transform(const QgsExpressionContext & context,const QVariant & value) const597 QVariant QgsColorRampTransformer::transform( const QgsExpressionContext &context, const QVariant &value ) const
598 {
599   Q_UNUSED( context )
600 
601   if ( value.isNull() )
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 
toExpression(const QString & baseExpression) const618 QString 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 
color(double value) const631 QColor 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 
colorRamp() const642 QgsColorRamp *QgsColorRampTransformer::colorRamp() const
643 {
644   return mGradientRamp.get();
645 }
646 
setColorRamp(QgsColorRamp * ramp)647 void QgsColorRampTransformer::setColorRamp( QgsColorRamp *ramp )
648 {
649   mGradientRamp.reset( ramp );
650 }
651 
652 
653 //
654 // QgsCurveTransform
655 //
656 
sortByX(const QgsPointXY & a,const QgsPointXY & b)657 bool sortByX( const QgsPointXY &a, const QgsPointXY &b )
658 {
659   return a.x() < b.x();
660 }
661 
QgsCurveTransform()662 QgsCurveTransform::QgsCurveTransform()
663 {
664   mControlPoints << QgsPointXY( 0, 0 ) << QgsPointXY( 1, 1 );
665   calcSecondDerivativeArray();
666 }
667 
QgsCurveTransform(const QList<QgsPointXY> & controlPoints)668 QgsCurveTransform::QgsCurveTransform( const QList<QgsPointXY> &controlPoints )
669   : mControlPoints( controlPoints )
670 {
671   std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
672   calcSecondDerivativeArray();
673 }
674 
~QgsCurveTransform()675 QgsCurveTransform::~QgsCurveTransform()
676 {
677   delete [] mSecondDerivativeArray;
678 }
679 
QgsCurveTransform(const QgsCurveTransform & other)680 QgsCurveTransform::QgsCurveTransform( const QgsCurveTransform &other )
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 
operator =(const QgsCurveTransform & other)690 QgsCurveTransform &QgsCurveTransform::operator=( const QgsCurveTransform &other )
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 
setControlPoints(const QList<QgsPointXY> & points)705 void 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 
addControlPoint(double x,double y)717 void 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 
removeControlPoint(double x,double y)728 void QgsCurveTransform::removeControlPoint( double x, double y )
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 
y(double x) const746 double 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 
y(const QVector<double> & x) const808 QVector<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 
readXml(const QDomElement & elem,const QDomDocument &)876 bool 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 
writeXml(QDomElement & transformElem,QDomDocument &) const902 bool 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 
toVariant() const919 QVariant QgsCurveTransform::toVariant() const
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 
loadVariant(const QVariant & transformer)938 bool 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 
calcSecondDerivativeArray()970 void 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 
1040