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