QGIS API Documentation  3.27.0-Master (e113457133)
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 
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 
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 
48 QgsPropertyTransformer::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 
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 
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 
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 
113 double QgsPropertyTransformer::transformNumeric( double input ) const
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 
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 
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 
165 bool 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 
178 double 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 
190 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 
211 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 
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 //
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 
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 
341 bool 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 
356 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 
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 
394 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 
415 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 
438 QgsSizeScaleTransformer *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
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  }
518 }
519 
520 
521 //
522 // QgsColorRampTransformer
523 //
524 
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 
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 
580 bool 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 
597 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 
618 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 
631 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 
643 {
644  return mGradientRamp.get();
645 }
646 
648 {
649  mGradientRamp.reset( ramp );
650 }
651 
652 
653 //
654 // QgsCurveTransform
655 //
656 
657 bool 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 
668 QgsCurveTransform::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 
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 
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 
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 
746 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 
808 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 
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 
902 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 
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 
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 
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 
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)
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:2199
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2260
bool sortByX(const QgsPointXY &a, const QgsPointXY &b)