QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
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 qBound( mMinOutput, input, mMaxOutput );
182 
183  input = transformNumeric( input );
184  if ( qgsDoubleNear( mExponent, 1.0 ) )
185  return mMinOutput + ( qBound( mMinValue, input, mMaxValue ) - mMinValue ) * ( mMaxOutput - mMinOutput ) / ( mMaxValue - mMinValue );
186  else
187  return mMinOutput + std::pow( qBound( mMinValue, input, 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 + ( qBound( mMinValue, value, mMaxValue ) - mMinValue ) * ( mMaxSize - mMinSize ) / ( mMaxValue - mMinValue );
364 
365  case Area:
366  case Flannery:
367  case Exponential:
368  return mMinSize + std::pow( qBound( mMinValue, value, 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  }
517  return new QgsSizeScaleTransformer( type, minValue, maxValue, minSize, maxSize, nullSize, exponent );
518 }
519 
520 
521 //
522 // QgsColorRampTransformer
523 //
524 
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 = qBound( 0.0, ( value - mMinValue ) / ( mMaxValue - mMinValue ), 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  mControlPoints = other.mControlPoints;
693  if ( other.mSecondDerivativeArray )
694  {
695  delete [] mSecondDerivativeArray;
696  mSecondDerivativeArray = new double[ mControlPoints.count()];
697  memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
698  }
699  return *this;
700 }
701 
702 void QgsCurveTransform::setControlPoints( const QList<QgsPointXY> &points )
703 {
704  mControlPoints = points;
705  std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
706  for ( int i = 0; i < mControlPoints.count(); ++i )
707  {
708  mControlPoints[ i ] = QgsPointXY( qBound( 0.0, mControlPoints.at( i ).x(), 1.0 ),
709  qBound( 0.0, mControlPoints.at( i ).y(), 1.0 ) );
710  }
711  calcSecondDerivativeArray();
712 }
713 
714 void QgsCurveTransform::addControlPoint( double x, double y )
715 {
716  QgsPointXY point( x, y );
717  if ( mControlPoints.contains( point ) )
718  return;
719 
720  mControlPoints << point;
721  std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
722  calcSecondDerivativeArray();
723 }
724 
725 void QgsCurveTransform::removeControlPoint( double x, double y )
726 {
727  for ( int i = 0; i < mControlPoints.count(); ++i )
728  {
729  if ( qgsDoubleNear( mControlPoints.at( i ).x(), x )
730  && qgsDoubleNear( mControlPoints.at( i ).y(), y ) )
731  {
732  mControlPoints.removeAt( i );
733  break;
734  }
735  }
736  calcSecondDerivativeArray();
737 }
738 
739 // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
740 // which in turn was adapted from
741 // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
742 
743 double QgsCurveTransform::y( double x ) const
744 {
745  int n = mControlPoints.count();
746  if ( n < 2 )
747  return qBound( 0.0, x, 1.0 ); // invalid
748  else if ( n < 3 )
749  {
750  // linear
751  if ( x <= mControlPoints.at( 0 ).x() )
752  return qBound( 0.0, mControlPoints.at( 0 ).y(), 1.0 );
753  else if ( x >= mControlPoints.at( n - 1 ).x() )
754  return qBound( 0.0, mControlPoints.at( 1 ).y(), 1.0 );
755  else
756  {
757  double dx = mControlPoints.at( 1 ).x() - mControlPoints.at( 0 ).x();
758  double dy = mControlPoints.at( 1 ).y() - mControlPoints.at( 0 ).y();
759  return qBound( 0.0, ( x - mControlPoints.at( 0 ).x() ) * ( dy / dx ) + mControlPoints.at( 0 ).y(), 1.0 );
760  }
761  }
762 
763  // safety check
764  if ( x <= mControlPoints.at( 0 ).x() )
765  return qBound( 0.0, mControlPoints.at( 0 ).y(), 1.0 );
766  if ( x >= mControlPoints.at( n - 1 ).x() )
767  return qBound( 0.0, mControlPoints.at( n - 1 ).y(), 1.0 );
768 
769  // find corresponding segment
770  QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
771  QgsPointXY currentControlPoint = *pointIt;
772  ++pointIt;
773  QgsPointXY nextControlPoint = *pointIt;
774 
775  for ( int i = 0; i < n - 1; ++i )
776  {
777  if ( x < nextControlPoint.x() )
778  {
779  // found segment
780  double h = nextControlPoint.x() - currentControlPoint.x();
781  double t = ( x - currentControlPoint.x() ) / h;
782 
783  double a = 1 - t;
784 
785  return qBound( 0.0, a * currentControlPoint.y() + t * nextControlPoint.y() + ( h * h / 6 ) * ( ( a * a * a - a ) * mSecondDerivativeArray[i] + ( t * t * t - t ) * mSecondDerivativeArray[i + 1] ),
786  1.0 );
787  }
788 
789  ++pointIt;
790  if ( pointIt == mControlPoints.constEnd() )
791  break;
792 
793  currentControlPoint = nextControlPoint;
794  nextControlPoint = *pointIt;
795  }
796 
797  //should not happen
798  return qBound( 0.0, x, 1.0 );
799 }
800 
801 // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
802 // which in turn was adapted from
803 // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
804 
805 QVector<double> QgsCurveTransform::y( const QVector<double> &x ) const
806 {
807  QVector<double> result;
808 
809  int n = mControlPoints.count();
810  if ( n < 3 )
811  {
812  // invalid control points - use simple transform
813  const auto constX = x;
814  for ( double i : constX )
815  result << y( i );
816 
817  return result;
818  }
819 
820  // find corresponding segment
821  QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
822  QgsPointXY currentControlPoint = *pointIt;
823  ++pointIt;
824  QgsPointXY nextControlPoint = *pointIt;
825 
826  int xIndex = 0;
827  double currentX = x.at( xIndex );
828  // safety check
829  while ( currentX <= currentControlPoint.x() )
830  {
831  result << qBound( 0.0, currentControlPoint.y(), 1.0 );
832  xIndex++;
833  currentX = x.at( xIndex );
834  }
835 
836  for ( int i = 0; i < n - 1; ++i )
837  {
838  while ( currentX < nextControlPoint.x() )
839  {
840  // found segment
841  double h = nextControlPoint.x() - currentControlPoint.x();
842 
843  double t = ( currentX - currentControlPoint.x() ) / h;
844 
845  double a = 1 - t;
846 
847  result << qBound( 0.0, a * currentControlPoint.y() + t * nextControlPoint.y() + ( h * h / 6 ) * ( ( a * a * a - a )*mSecondDerivativeArray[i] + ( t * t * t - t )*mSecondDerivativeArray[i + 1] ), 1.0 );
848  xIndex++;
849  if ( xIndex == x.count() )
850  return result;
851 
852  currentX = x.at( xIndex );
853  }
854 
855  ++pointIt;
856  if ( pointIt == mControlPoints.constEnd() )
857  break;
858 
859  currentControlPoint = nextControlPoint;
860  nextControlPoint = *pointIt;
861  }
862 
863  // safety check
864  while ( xIndex < x.count() )
865  {
866  result << qBound( 0.0, nextControlPoint.y(), 1.0 );
867  xIndex++;
868  }
869 
870  return result;
871 }
872 
873 bool QgsCurveTransform::readXml( const QDomElement &elem, const QDomDocument & )
874 {
875  QString xString = elem.attribute( QStringLiteral( "x" ) );
876  QString yString = elem.attribute( QStringLiteral( "y" ) );
877 
878  QStringList xVals = xString.split( ',' );
879  QStringList yVals = yString.split( ',' );
880  if ( xVals.count() != yVals.count() )
881  return false;
882 
883  QList< QgsPointXY > newPoints;
884  bool ok = false;
885  for ( int i = 0; i < xVals.count(); ++i )
886  {
887  double x = xVals.at( i ).toDouble( &ok );
888  if ( !ok )
889  return false;
890  double y = yVals.at( i ).toDouble( &ok );
891  if ( !ok )
892  return false;
893  newPoints << QgsPointXY( x, y );
894  }
895  setControlPoints( newPoints );
896  return true;
897 }
898 
899 bool QgsCurveTransform::writeXml( QDomElement &transformElem, QDomDocument & ) const
900 {
901  QStringList x;
902  QStringList y;
903  const auto constMControlPoints = mControlPoints;
904  for ( const QgsPointXY &p : constMControlPoints )
905  {
906  x << qgsDoubleToString( p.x() );
907  y << qgsDoubleToString( p.y() );
908  }
909 
910  transformElem.setAttribute( QStringLiteral( "x" ), x.join( ',' ) );
911  transformElem.setAttribute( QStringLiteral( "y" ), y.join( ',' ) );
912 
913  return true;
914 }
915 
917 {
918  QVariantMap transformMap;
919 
920  QStringList x;
921  QStringList y;
922  const auto constMControlPoints = mControlPoints;
923  for ( const QgsPointXY &p : constMControlPoints )
924  {
925  x << qgsDoubleToString( p.x() );
926  y << qgsDoubleToString( p.y() );
927  }
928 
929  transformMap.insert( QStringLiteral( "x" ), x.join( ',' ) );
930  transformMap.insert( QStringLiteral( "y" ), y.join( ',' ) );
931 
932  return transformMap;
933 }
934 
935 bool QgsCurveTransform::loadVariant( const QVariant &transformer )
936 {
937  QVariantMap transformMap = transformer.toMap();
938 
939  QString xString = transformMap.value( QStringLiteral( "x" ) ).toString();
940  QString yString = transformMap.value( QStringLiteral( "y" ) ).toString();
941 
942  QStringList xVals = xString.split( ',' );
943  QStringList yVals = yString.split( ',' );
944  if ( xVals.count() != yVals.count() )
945  return false;
946 
947  QList< QgsPointXY > newPoints;
948  bool ok = false;
949  for ( int i = 0; i < xVals.count(); ++i )
950  {
951  double x = xVals.at( i ).toDouble( &ok );
952  if ( !ok )
953  return false;
954  double y = yVals.at( i ).toDouble( &ok );
955  if ( !ok )
956  return false;
957  newPoints << QgsPointXY( x, y );
958  }
959  setControlPoints( newPoints );
960  return true;
961 }
962 
963 // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
964 // which in turn was adapted from
965 // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
966 
967 void QgsCurveTransform::calcSecondDerivativeArray()
968 {
969  int n = mControlPoints.count();
970  if ( n < 3 )
971  return; // cannot proceed
972 
973  delete[] mSecondDerivativeArray;
974 
975  double *matrix = new double[ n * 3 ];
976  double *result = new double[ n ];
977  matrix[0] = 0;
978  matrix[1] = 1;
979  matrix[2] = 0;
980  result[0] = 0;
981  QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
982  QgsPointXY pointIm1 = *pointIt;
983  ++pointIt;
984  QgsPointXY pointI = *pointIt;
985  ++pointIt;
986  QgsPointXY pointIp1 = *pointIt;
987 
988  for ( int i = 1; i < n - 1; ++i )
989  {
990  matrix[i * 3 + 0 ] = ( pointI.x() - pointIm1.x() ) / 6.0;
991  matrix[i * 3 + 1 ] = ( pointIp1.x() - pointIm1.x() ) / 3.0;
992  matrix[i * 3 + 2 ] = ( pointIp1.x() - pointI.x() ) / 6.0;
993  result[i] = ( pointIp1.y() - pointI.y() ) / ( pointIp1.x() - pointI.x() ) - ( pointI.y() - pointIm1.y() ) / ( pointI.x() - pointIm1.x() );
994 
995  // shuffle points
996  pointIm1 = pointI;
997  pointI = pointIp1;
998  ++pointIt;
999  if ( pointIt == mControlPoints.constEnd() )
1000  break;
1001 
1002  pointIp1 = *pointIt;
1003  }
1004  matrix[( n - 1 ) * 3 + 0] = 0;
1005  matrix[( n - 1 ) * 3 + 1] = 1;
1006  matrix[( n - 1 ) * 3 + 2] = 0;
1007  result[n - 1] = 0;
1008 
1009  // solving pass1 (up->down)
1010  for ( int i = 1; i < n; ++i )
1011  {
1012  double k = matrix[i * 3 + 0] / matrix[( i - 1 ) * 3 + 1];
1013  matrix[i * 3 + 1] -= k * matrix[( i - 1 ) * 3 + 2];
1014  matrix[i * 3 + 0] = 0;
1015  result[i] -= k * result[i - 1];
1016  }
1017  // solving pass2 (down->up)
1018  for ( int i = n - 2; i >= 0; --i )
1019  {
1020  double k = matrix[i * 3 + 2] / matrix[( i + 1 ) * 3 + 1];
1021  matrix[i * 3 + 1] -= k * matrix[( i + 1 ) * 3 + 0];
1022  matrix[i * 3 + 2] = 0;
1023  result[i] -= k * result[i + 1];
1024  }
1025 
1026  // return second derivative value for each point
1027  mSecondDerivativeArray = new double[n];
1028  for ( int i = 0; i < n; ++i )
1029  {
1030  mSecondDerivativeArray[i] = result[i] / matrix[( i * 3 ) + 1];
1031  }
1032 
1033  delete[] result;
1034  delete[] matrix;
1035 }
1036 
Class for parsing and evaluation of expressions (formerly called "search strings").
ScaleType
Size scaling methods.
void setType(ScaleType type)
Sets the size transformer&#39;s scaling type (the method used to calculate the size from a value)...
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
bool sortByX(const QgsPointXY &a, const QgsPointXY &b)
double mMinValue
Minimum value expected by the transformer.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
bool readXml(const QDomElement &elem, const QDomDocument &doc)
Reads the curve&#39;s state from an XML element.
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.
std::unique_ptr< QgsCurveTransform > mCurveTransform
Optional curve transform.
virtual QVariant toVariant() const
Saves this transformer to a QVariantMap, wrapped in a QVariant.
Size scaling transformer (QgsSizeScaleTransformer)
QgsSizeScaleTransformer * clone() const override
Returns a clone of the transformer.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
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.
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
QgsPropertyTransformer subclass for scaling an input numeric value into an output numeric value...
QVariant evaluate()
Evaluate the feature and return the result.
Abstract base class for objects which transform the calculated value of a property.
void setControlPoints(const QList< QgsPointXY > &points)
Sets the list of control points for the transform.
double exponent() const
Returns the exponent for an exponential expression.
bool writeXml(QDomElement &transformElem, QDomDocument &doc) const
Writes the current state of the transform into an XML element.
static QgsPropertyTransformer * create(Type type)
Factory method for creating a new property transformer of the specified type.
QgsGenericNumericTransformer * clone() const override
Returns a clone of the transformer.
static QgsGenericNumericTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
QgsColorRampTransformer(double minValue=0.0, double maxValue=1.0, QgsColorRamp *ramp=nullptr, const QColor &nullColor=QColor(0, 0, 0, 0))
Constructor for QgsColorRampTransformer.
QgsPropertyTransformer(double minValue=0.0, double maxValue=1.0)
Constructor for QgsPropertyTransformer.
static QgsPropertyTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding property transformer.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsColorRampTransformer & operator=(const QgsColorRampTransformer &other)
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
QgsPropertyTransformer & operator=(const QgsPropertyTransformer &other)
virtual ~QgsPropertyTransformer()
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
static QVariant colorRampToVariant(const QString &name, QgsColorRamp *ramp)
Saves a color ramp to a QVariantMap, wrapped in a QVariant.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:275
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsCurveTransform()
Constructs a default QgsCurveTransform which linearly maps values between 0 and 1 unchanged...
static QgsSizeScaleTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
double minValue() const
Returns the minimum value expected by the transformer.
An expression node which takes it value from a feature&#39;s field.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
An expression node for expression functions.
QVariant toVariant() const
Saves this curve transformer to a QVariantMap, wrapped in a QVariant.
static const QList< QgsExpressionFunction * > & Functions()
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
double value(double input) const
Calculates the size corresponding to a specific input value.
double x
Definition: qgspointxy.h:47
QgsColorRamp * colorRamp() const
Returns the color ramp used for calculating property colors.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
Color ramp transformer (QgsColorRampTransformer)
double maxSize() const
Returns the maximum calculated size.
QString dump() const override
Dump this node into a serialized (part) of an expression.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
double mMaxValue
Maximum value expected by the transformer.
Generic transformer for numeric values (QgsGenericNumericTransformer)
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
QgsCurveTransform & operator=(const QgsCurveTransform &other)
double y(double x) const
Returns the mapped y value corresponding to the specified x value.
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
double exponent() const
Returns the exponent for an exponential expression.
Handles scaling of input values to output values by using a curve created from smoothly joining a num...
bool loadVariant(const QVariant &transformer)
Load this curve transformer from a QVariantMap, wrapped in a QVariant.
QgsColorRampTransformer * clone() const override
Returns a clone of the transformer.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp to use for calculating property colors.
double size(double value) const
Calculates the size corresponding to a specific value.
QgsPropertyTransformer subclass for scaling a value into a size according to various scaling methods...
double nullSize() const
Returns the size value when an expression evaluates to NULL.
double transformNumeric(double input) const
Applies base class numeric transformations.
void removeControlPoint(double x, double y)
Removes a control point from the transform.
QColor color(double value) const
Calculates the color corresponding to a specific value.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required...
void addControlPoint(double x, double y)
Adds a control point to the transform.
double minSize() const
Returns the minimum calculated size.
QgsPropertyTransformer subclass for transforming a numeric value into a color from a color ramp...
virtual bool loadVariant(const QVariant &transformer)
Loads this transformer from a QVariantMap, wrapped in a QVariant.
static QColor decodeColor(const QString &str)
int fnIndex() const
Returns the index of the node&#39;s function.
double maxValue() const
Returns the maximum value expected by the transformer.
ScaleType type() const
Returns the size transformer&#39;s scaling type (the method used to calculate the size from a value)...