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