QGIS API Documentation  3.2.0-Bonn (bc43194)
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 "qgsfeature.h"
22 #include "qgssymbollayerutils.h"
23 #include "qgscolorramp.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  Q_FOREACH ( double i, x )
809  result << y( i );
810 
811  return result;
812  }
813 
814  // find corresponding segment
815  QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
816  QgsPointXY currentControlPoint = *pointIt;
817  ++pointIt;
818  QgsPointXY nextControlPoint = *pointIt;
819 
820  int xIndex = 0;
821  double currentX = x.at( xIndex );
822  // safety check
823  while ( currentX <= currentControlPoint.x() )
824  {
825  result << qBound( 0.0, currentControlPoint.y(), 1.0 );
826  xIndex++;
827  currentX = x.at( xIndex );
828  }
829 
830  for ( int i = 0; i < n - 1; ++i )
831  {
832  while ( currentX < nextControlPoint.x() )
833  {
834  // found segment
835  double h = nextControlPoint.x() - currentControlPoint.x();
836 
837  double t = ( currentX - currentControlPoint.x() ) / h;
838 
839  double a = 1 - t;
840 
841  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 );
842  xIndex++;
843  if ( xIndex == x.count() )
844  return result;
845 
846  currentX = x.at( xIndex );
847  }
848 
849  ++pointIt;
850  if ( pointIt == mControlPoints.constEnd() )
851  break;
852 
853  currentControlPoint = nextControlPoint;
854  nextControlPoint = *pointIt;
855  }
856 
857  // safety check
858  while ( xIndex < x.count() )
859  {
860  result << qBound( 0.0, nextControlPoint.y(), 1.0 );
861  xIndex++;
862  }
863 
864  return result;
865 }
866 
867 bool QgsCurveTransform::readXml( const QDomElement &elem, const QDomDocument & )
868 {
869  QString xString = elem.attribute( QStringLiteral( "x" ) );
870  QString yString = elem.attribute( QStringLiteral( "y" ) );
871 
872  QStringList xVals = xString.split( ',' );
873  QStringList yVals = yString.split( ',' );
874  if ( xVals.count() != yVals.count() )
875  return false;
876 
877  QList< QgsPointXY > newPoints;
878  bool ok = false;
879  for ( int i = 0; i < xVals.count(); ++i )
880  {
881  double x = xVals.at( i ).toDouble( &ok );
882  if ( !ok )
883  return false;
884  double y = yVals.at( i ).toDouble( &ok );
885  if ( !ok )
886  return false;
887  newPoints << QgsPointXY( x, y );
888  }
889  setControlPoints( newPoints );
890  return true;
891 }
892 
893 bool QgsCurveTransform::writeXml( QDomElement &transformElem, QDomDocument & ) const
894 {
895  QStringList x;
896  QStringList y;
897  Q_FOREACH ( const QgsPointXY &p, mControlPoints )
898  {
899  x << qgsDoubleToString( p.x() );
900  y << qgsDoubleToString( p.y() );
901  }
902 
903  transformElem.setAttribute( QStringLiteral( "x" ), x.join( ',' ) );
904  transformElem.setAttribute( QStringLiteral( "y" ), y.join( ',' ) );
905 
906  return true;
907 }
908 
910 {
911  QVariantMap transformMap;
912 
913  QStringList x;
914  QStringList y;
915  Q_FOREACH ( const QgsPointXY &p, mControlPoints )
916  {
917  x << qgsDoubleToString( p.x() );
918  y << qgsDoubleToString( p.y() );
919  }
920 
921  transformMap.insert( QStringLiteral( "x" ), x.join( ',' ) );
922  transformMap.insert( QStringLiteral( "y" ), y.join( ',' ) );
923 
924  return transformMap;
925 }
926 
927 bool QgsCurveTransform::loadVariant( const QVariant &transformer )
928 {
929  QVariantMap transformMap = transformer.toMap();
930 
931  QString xString = transformMap.value( QStringLiteral( "x" ) ).toString();
932  QString yString = transformMap.value( QStringLiteral( "y" ) ).toString();
933 
934  QStringList xVals = xString.split( ',' );
935  QStringList yVals = yString.split( ',' );
936  if ( xVals.count() != yVals.count() )
937  return false;
938 
939  QList< QgsPointXY > newPoints;
940  bool ok = false;
941  for ( int i = 0; i < xVals.count(); ++i )
942  {
943  double x = xVals.at( i ).toDouble( &ok );
944  if ( !ok )
945  return false;
946  double y = yVals.at( i ).toDouble( &ok );
947  if ( !ok )
948  return false;
949  newPoints << QgsPointXY( x, y );
950  }
951  setControlPoints( newPoints );
952  return true;
953 }
954 
955 // this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
956 // which in turn was adapted from
957 // http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
958 
959 void QgsCurveTransform::calcSecondDerivativeArray()
960 {
961  int n = mControlPoints.count();
962  if ( n < 3 )
963  return; // cannot proceed
964 
965  delete[] mSecondDerivativeArray;
966 
967  double *matrix = new double[ n * 3 ];
968  double *result = new double[ n ];
969  matrix[0] = 0;
970  matrix[1] = 1;
971  matrix[2] = 0;
972  result[0] = 0;
973  QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
974  QgsPointXY pointIm1 = *pointIt;
975  ++pointIt;
976  QgsPointXY pointI = *pointIt;
977  ++pointIt;
978  QgsPointXY pointIp1 = *pointIt;
979 
980  for ( int i = 1; i < n - 1; ++i )
981  {
982  matrix[i * 3 + 0 ] = ( pointI.x() - pointIm1.x() ) / 6.0;
983  matrix[i * 3 + 1 ] = ( pointIp1.x() - pointIm1.x() ) / 3.0;
984  matrix[i * 3 + 2 ] = ( pointIp1.x() - pointI.x() ) / 6.0;
985  result[i] = ( pointIp1.y() - pointI.y() ) / ( pointIp1.x() - pointI.x() ) - ( pointI.y() - pointIm1.y() ) / ( pointI.x() - pointIm1.x() );
986 
987  // shuffle points
988  pointIm1 = pointI;
989  pointI = pointIp1;
990  ++pointIt;
991  if ( pointIt == mControlPoints.constEnd() )
992  break;
993 
994  pointIp1 = *pointIt;
995  }
996  matrix[( n - 1 ) * 3 + 0] = 0;
997  matrix[( n - 1 ) * 3 + 1] = 1;
998  matrix[( n - 1 ) * 3 + 2] = 0;
999  result[n - 1] = 0;
1000 
1001  // solving pass1 (up->down)
1002  for ( int i = 1; i < n; ++i )
1003  {
1004  double k = matrix[i * 3 + 0] / matrix[( i - 1 ) * 3 + 1];
1005  matrix[i * 3 + 1] -= k * matrix[( i - 1 ) * 3 + 2];
1006  matrix[i * 3 + 0] = 0;
1007  result[i] -= k * result[i - 1];
1008  }
1009  // solving pass2 (down->up)
1010  for ( int i = n - 2; i >= 0; --i )
1011  {
1012  double k = matrix[i * 3 + 2] / matrix[( i + 1 ) * 3 + 1];
1013  matrix[i * 3 + 1] -= k * matrix[( i + 1 ) * 3 + 0];
1014  matrix[i * 3 + 2] = 0;
1015  result[i] -= k * result[i + 1];
1016  }
1017 
1018  // return second derivative value for each point
1019  mSecondDerivativeArray = new double[n];
1020  for ( int i = 0; i < n; ++i )
1021  {
1022  mSecondDerivativeArray[i] = result[i] / matrix[( i * 3 ) + 1];
1023  }
1024 
1025  delete[] result;
1026  delete[] matrix;
1027 }
1028 
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:251
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:237
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 root node of the expression. Root node is null is parsing has failed.
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)...