QGIS API Documentation 4.3.0-Master (16eddb2670e)
Loading...
Searching...
No Matches
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
17
18#include <memory>
19
20#include "qgscolorramp.h"
21#include "qgscolorutils.h"
22#include "qgsexpression.h"
24#include "qgslogger.h"
25#include "qgspointxy.h"
26#include "qgssymbollayerutils.h"
27
28#include <QString>
29
30using namespace Qt::StringLiterals;
31
32//
33// QgsPropertyTransformer
34//
35
37{
38 QgsPropertyTransformer *transformer = nullptr;
39 switch ( type )
40 {
42 transformer = new QgsGenericNumericTransformer();
43 break;
45 transformer = new QgsSizeScaleTransformer();
46 break;
48 transformer = new QgsColorRampTransformer();
49 break;
50 }
51 return transformer;
52}
53
58
64
66{
67 if ( &other == this )
68 return *this;
69
70 mMinValue = other.mMinValue;
71 mMaxValue = other.mMaxValue;
72 mCurveTransform.reset( other.mCurveTransform ? new QgsCurveTransform( *other.mCurveTransform ) : nullptr );
73 return *this;
74}
75
77
78bool QgsPropertyTransformer::loadVariant( const QVariant &transformer )
79{
80 QVariantMap transformerMap = transformer.toMap();
81
82 mMinValue = transformerMap.value( u"minValue"_s, 0.0 ).toDouble();
83 mMaxValue = transformerMap.value( u"maxValue"_s, 1.0 ).toDouble();
84 mCurveTransform.reset( nullptr );
85
86 QVariantMap curve = transformerMap.value( u"curve"_s ).toMap();
87
88 if ( !curve.isEmpty() )
89 {
90 mCurveTransform = std::make_unique<QgsCurveTransform>();
91 mCurveTransform->loadVariant( curve );
92 }
93
94 return true;
95}
96
98{
99 QVariantMap transformerMap;
100
101 transformerMap.insert( u"minValue"_s, mMinValue );
102 transformerMap.insert( u"maxValue"_s, mMaxValue );
103
104 if ( mCurveTransform )
105 {
106 transformerMap.insert( u"curve"_s, mCurveTransform->toVariant() );
107 }
108 return transformerMap;
109}
110
111QgsPropertyTransformer *QgsPropertyTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName )
112{
113 baseExpression.clear();
114 fieldName.clear();
115
116 if ( QgsPropertyTransformer *sizeScale = QgsSizeScaleTransformer::fromExpression( expression, baseExpression, fieldName ) )
117 return sizeScale;
118 else
119 return nullptr;
120}
121
123{
124 if ( !mCurveTransform )
125 return input;
126
128 return input;
129
130 // convert input into target range
131 double scaledInput = ( input - mMinValue ) / ( mMaxValue - mMinValue );
132
133 return mMinValue + ( mMaxValue - mMinValue ) * mCurveTransform->y( scaledInput );
134}
135
136
137//
138// QgsGenericNumericTransformer
139//
140
141QgsGenericNumericTransformer::QgsGenericNumericTransformer( double minValue, double maxValue, double minOutput, double maxOutput, double nullOutput, double exponent )
143 , mMinOutput( minOutput )
144 , mMaxOutput( maxOutput )
145 , mNullOutput( nullOutput )
146 , mExponent( exponent )
147{}
148
150{
151 auto t = std::make_unique<QgsGenericNumericTransformer>( mMinValue, mMaxValue, mMinOutput, mMaxOutput, mNullOutput, mExponent );
152 if ( mCurveTransform )
153 t->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
154 return t.release();
155}
156
158{
159 QVariantMap transformerMap = QgsPropertyTransformer::toVariant().toMap();
160
161 transformerMap.insert( u"minOutput"_s, mMinOutput );
162 transformerMap.insert( u"maxOutput"_s, mMaxOutput );
163 transformerMap.insert( u"nullOutput"_s, mNullOutput );
164 transformerMap.insert( u"exponent"_s, mExponent );
165
166 return transformerMap;
167}
168
169bool QgsGenericNumericTransformer::loadVariant( const QVariant &transformer )
170{
172
173 QVariantMap transformerMap = transformer.toMap();
174
175 mMinOutput = transformerMap.value( u"minOutput"_s, 0.0 ).toDouble();
176 mMaxOutput = transformerMap.value( u"maxOutput"_s, 1.0 ).toDouble();
177 mNullOutput = transformerMap.value( u"nullOutput"_s, 0.0 ).toDouble();
178 mExponent = transformerMap.value( u"exponent"_s, 1.0 ).toDouble();
179 return true;
180}
181
182double QgsGenericNumericTransformer::value( double input ) const
183{
185 {
186 return mMinOutput;
187 }
188
189 input = transformNumeric( input );
190 if ( qgsDoubleNear( mExponent, 1.0 ) )
191 return mMinOutput + ( std::clamp( input, mMinValue, mMaxValue ) - mMinValue ) * ( mMaxOutput - mMinOutput ) / ( mMaxValue - mMinValue );
192 else
193 return mMinOutput + std::pow( std::clamp( input, mMinValue, mMaxValue ) - mMinValue, mExponent ) * ( mMaxOutput - mMinOutput ) / std::pow( mMaxValue - mMinValue, mExponent );
194}
195
196QVariant QgsGenericNumericTransformer::transform( const QgsExpressionContext &context, const QVariant &v ) const
197{
198 Q_UNUSED( context )
199
200 if ( QgsVariantUtils::isNull( v ) )
201 return mNullOutput;
202
203 bool ok;
204 double dblValue = v.toDouble( &ok );
205
206 if ( ok )
207 {
208 //apply scaling to value
209 return value( dblValue );
210 }
211 else
212 {
213 return v;
214 }
215}
216
217QString QgsGenericNumericTransformer::toExpression( const QString &baseExpression ) const
218{
219 QString minValueString = QString::number( mMinValue );
220 QString maxValueString = QString::number( mMaxValue );
221 QString minOutputString = QString::number( mMinOutput );
222 QString maxOutputString = QString::number( mMaxOutput );
223 QString nullOutputString = QString::number( mNullOutput );
224 QString exponentString = QString::number( mExponent );
225
226 if ( qgsDoubleNear( mExponent, 1.0 ) )
227 return u"coalesce(scale_linear(%1, %2, %3, %4, %5), %6)"_s.arg( baseExpression, minValueString, maxValueString, minOutputString, maxOutputString, nullOutputString );
228 else
229 return u"coalesce(scale_polynomial(%1, %2, %3, %4, %5, %6), %7)"_s.arg( baseExpression, minValueString, maxValueString, minOutputString, maxOutputString, exponentString, nullOutputString );
230}
231
232QgsGenericNumericTransformer *QgsGenericNumericTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName ) // cppcheck-suppress duplInheritedMember
233{
234 bool ok = false;
235
236 double nullValue = 0.0;
237 double exponent = 1.0;
238
239 baseExpression.clear();
240 fieldName.clear();
241
242 QgsExpression e( expression );
243
244 if ( !e.rootNode() )
245 return nullptr;
246
247 const QgsExpressionNodeFunction *f = dynamic_cast<const QgsExpressionNodeFunction *>( e.rootNode() );
248 if ( !f )
249 return nullptr;
250
251 QList<QgsExpressionNode *> args = f->args()->list();
252
253 // the scale function may be enclosed in a coalesce(expr, 0) to avoid NULL value
254 // to be drawn with the default size
255
257
258 if ( "coalesce" == fd->name() )
259 {
260 f = dynamic_cast<const QgsExpressionNodeFunction *>( args[0] );
261 if ( !f )
262 return nullptr;
263 fd = f->function();
264 nullValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
265 if ( !ok )
266 return nullptr;
267 args = f->args()->list();
268 }
269
270 if ( "scale_linear" == fd->name() )
271 {
272 exponent = 1.0;
273 }
274 else if ( "scale_polynomial" == fd->name() )
275 {
276 exponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
277 }
278 else
279 {
280 return nullptr;
281 }
282
283 bool expOk = true;
284 double minValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
285 expOk &= ok;
286 double maxValue = QgsExpression( args[2]->dump() ).evaluate().toDouble( &ok );
287 expOk &= ok;
288 double minOutput = QgsExpression( args[3]->dump() ).evaluate().toDouble( &ok );
289 expOk &= ok;
290 double maxOutput = QgsExpression( args[4]->dump() ).evaluate().toDouble( &ok );
291 expOk &= ok;
292
293 if ( !expOk )
294 {
295 return nullptr;
296 }
297
298 if ( args[0]->nodeType() == QgsExpressionNode::ntColumnRef )
299 {
300 fieldName = static_cast< QgsExpressionNodeColumnRef * >( args[0] )->name();
301 }
302 else
303 {
304 baseExpression = args[0]->dump();
305 }
306 return new QgsGenericNumericTransformer( minValue, maxValue, minOutput, maxOutput, nullValue, exponent );
307}
308
309
310//
311// QgsSizeScaleProperty
312//
315 , mMinSize( minSize )
316 , mMaxSize( maxSize )
317 , mNullSize( nullSize )
318 , mExponent( exponent )
319{
320 setType( type );
321}
322
324{
325 auto t = std::make_unique<QgsSizeScaleTransformer>( mType, mMinValue, mMaxValue, mMinSize, mMaxSize, mNullSize, mExponent );
326 if ( mCurveTransform )
327 t->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
328 return t.release();
329}
330
332{
333 QVariantMap transformerMap = QgsPropertyTransformer::toVariant().toMap();
334
335 transformerMap.insert( u"scaleType"_s, static_cast< int >( mType ) );
336 transformerMap.insert( u"minSize"_s, mMinSize );
337 transformerMap.insert( u"maxSize"_s, mMaxSize );
338 transformerMap.insert( u"nullSize"_s, mNullSize );
339 transformerMap.insert( u"exponent"_s, mExponent );
340
341 return transformerMap;
342}
343
344bool QgsSizeScaleTransformer::loadVariant( const QVariant &transformer )
345{
347
348 QVariantMap transformerMap = transformer.toMap();
349
350 mType = static_cast< ScaleType >( transformerMap.value( u"scaleType"_s, Linear ).toInt() );
351 mMinSize = transformerMap.value( u"minSize"_s, 0.0 ).toDouble();
352 mMaxSize = transformerMap.value( u"maxSize"_s, 1.0 ).toDouble();
353 mNullSize = transformerMap.value( u"nullSize"_s, 0.0 ).toDouble();
354 mExponent = transformerMap.value( u"exponent"_s, 1.0 ).toDouble();
355
356 return true;
357}
358
359double QgsSizeScaleTransformer::size( double value ) const
360{
361 value = transformNumeric( value );
362
364 {
365 return mMinSize;
366 }
367
368 switch ( mType )
369 {
370 case Linear:
371 return mMinSize + ( std::clamp( value, mMinValue, mMaxValue ) - mMinValue ) * ( mMaxSize - mMinSize ) / ( mMaxValue - mMinValue );
372
373 case Area:
374 case Flannery:
375 case Exponential:
376 return mMinSize + std::pow( std::clamp( value, mMinValue, mMaxValue ) - mMinValue, mExponent ) * ( mMaxSize - mMinSize ) / std::pow( mMaxValue - mMinValue, mExponent );
377 }
378 return 0;
379}
380
382{
383 mType = type;
384 switch ( mType )
385 {
386 case Linear:
387 mExponent = 1.0;
388 break;
389 case Area:
390 mExponent = 0.5;
391 break;
392 case Flannery:
393 mExponent = 0.57;
394 break;
395 case Exponential:
396 //no change
397 break;
398 }
399}
400
401QVariant QgsSizeScaleTransformer::transform( const QgsExpressionContext &context, const QVariant &value ) const
402{
403 Q_UNUSED( context )
404
405 if ( QgsVariantUtils::isNull( value ) )
406 return mNullSize;
407
408 bool ok;
409 double dblValue = value.toDouble( &ok );
410
411 if ( ok )
412 {
413 //apply scaling to value
414 return size( dblValue );
415 }
416 else
417 {
418 return value;
419 }
420}
421
422QString QgsSizeScaleTransformer::toExpression( const QString &baseExpression ) const
423{
424 QString minValueString = QString::number( mMinValue );
425 QString maxValueString = QString::number( mMaxValue );
426 QString minSizeString = QString::number( mMinSize );
427 QString maxSizeString = QString::number( mMaxSize );
428 QString nullSizeString = QString::number( mNullSize );
429 QString exponentString = QString::number( mExponent );
430
431 switch ( mType )
432 {
433 case Linear:
434 return u"coalesce(scale_linear(%1, %2, %3, %4, %5), %6)"_s.arg( baseExpression, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );
435
436 case Area:
437 case Flannery:
438 case Exponential:
439 return u"coalesce(scale_polynomial(%1, %2, %3, %4, %5, %6), %7)"_s.arg( baseExpression, minValueString, maxValueString, minSizeString, maxSizeString, exponentString, nullSizeString );
440 }
441 return QString();
442}
443
444QgsSizeScaleTransformer *QgsSizeScaleTransformer::fromExpression( const QString &expression, QString &baseExpression, QString &fieldName ) // cppcheck-suppress duplInheritedMember
445{
446 bool ok = false;
447
449 double nullSize = 0.0;
450 double exponent = 1.0;
451
452 baseExpression.clear();
453 fieldName.clear();
454
455 QgsExpression e( expression );
456
457 if ( !e.rootNode() )
458 return nullptr;
459
460 const QgsExpressionNodeFunction *f = dynamic_cast<const QgsExpressionNodeFunction *>( e.rootNode() );
461 if ( !f )
462 return nullptr;
463
464 QList<QgsExpressionNode *> args = f->args()->list();
465
467
468 // the scale function may be enclosed in a coalesce(expr, 0) to avoid NULL value
469 // to be drawn with the default size
470 if ( "coalesce" == fd->name() )
471 {
472 f = dynamic_cast<const QgsExpressionNodeFunction *>( args[0] );
473 if ( !f )
474 return nullptr;
475 fd = f->function();
476 nullSize = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
477 if ( !ok )
478 return nullptr;
479 args = f->args()->list();
480 }
481
482 if ( "scale_linear" == fd->name() )
483 {
484 type = Linear;
485 }
486 else if ( "scale_polynomial" == fd->name() )
487 {
488 exponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
489 if ( !ok )
490 return nullptr;
491 if ( qgsDoubleNear( exponent, 0.57, 0.001 ) )
492 type = Flannery;
493 else if ( qgsDoubleNear( exponent, 0.5, 0.001 ) )
494 type = Area;
495 else
497 }
498 else
499 {
500 return nullptr;
501 }
502
503 bool expOk = true;
504 double minValue = QgsExpression( args[1]->dump() ).evaluate().toDouble( &ok );
505 expOk &= ok;
506 double maxValue = QgsExpression( args[2]->dump() ).evaluate().toDouble( &ok );
507 expOk &= ok;
508 double minSize = QgsExpression( args[3]->dump() ).evaluate().toDouble( &ok );
509 expOk &= ok;
510 double maxSize = QgsExpression( args[4]->dump() ).evaluate().toDouble( &ok );
511 expOk &= ok;
512
513 if ( !expOk )
514 {
515 return nullptr;
516 }
517
518 if ( args[0]->nodeType() == QgsExpressionNode::ntColumnRef )
519 {
520 fieldName = static_cast< QgsExpressionNodeColumnRef * >( args[0] )->name();
521 }
522 else
523 {
524 baseExpression = args[0]->dump();
525 }
527}
528
529
530//
531// QgsColorRampTransformer
532//
533
536 , mGradientRamp( ramp )
537 , mNullColor( nullColor )
538 , mRampName( rampName )
539{}
540
542 : QgsPropertyTransformer( other )
543 , mGradientRamp( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr )
544 , mNullColor( other.mNullColor )
545 , mRampName( other.mRampName )
546{}
547
549{
550 if ( &other == this )
551 return *this;
552
554 mMinValue = other.mMinValue;
555 mMaxValue = other.mMaxValue;
556 mGradientRamp.reset( other.mGradientRamp ? other.mGradientRamp->clone() : nullptr );
557 mNullColor = other.mNullColor;
558 mRampName = other.mRampName;
559 return *this;
560}
561
563{
564 auto c = std::make_unique<QgsColorRampTransformer>( mMinValue, mMaxValue, mGradientRamp ? mGradientRamp->clone() : nullptr, mNullColor );
565 c->setRampName( mRampName );
566 if ( mCurveTransform )
567 c->setCurveTransform( new QgsCurveTransform( *mCurveTransform ) );
568 return c.release();
569}
570
572{
573 QVariantMap transformerMap = QgsPropertyTransformer::toVariant().toMap();
574
575 if ( mGradientRamp )
576 {
577 transformerMap.insert( u"colorramp"_s, QgsSymbolLayerUtils::colorRampToVariant( u"[source]"_s, mGradientRamp.get() ) );
578 }
579 transformerMap.insert( u"nullColor"_s, QgsColorUtils::colorToString( mNullColor ) );
580 transformerMap.insert( u"rampName"_s, mRampName );
581
582 return transformerMap;
583}
584
585bool QgsColorRampTransformer::loadVariant( const QVariant &definition )
586{
587 QVariantMap transformerMap = definition.toMap();
588
590
591 mGradientRamp.reset( nullptr );
592 if ( transformerMap.contains( u"colorramp"_s ) )
593 {
594 setColorRamp( QgsSymbolLayerUtils::loadColorRamp( transformerMap.value( u"colorramp"_s ).toMap() ).release() );
595 }
596
597 mNullColor = QgsColorUtils::colorFromString( transformerMap.value( u"nullColor"_s, u"0,0,0,0"_s ).toString() );
598 mRampName = transformerMap.value( u"rampName"_s ).toString();
599 return true;
600}
601
602QVariant QgsColorRampTransformer::transform( const QgsExpressionContext &context, const QVariant &value ) const
603{
604 Q_UNUSED( context )
605
606 if ( QgsVariantUtils::isNull( value ) )
607 return mNullColor;
608
609 bool ok;
610 double dblValue = value.toDouble( &ok );
611
612 if ( ok )
613 {
614 //apply scaling to value
615 return color( dblValue );
616 }
617 else
618 {
619 return value;
620 }
621}
622
623QString QgsColorRampTransformer::toExpression( const QString &baseExpression ) const
624{
625 if ( !mGradientRamp )
626 return QgsExpression::quotedValue( mNullColor.name() );
627
628 QString minValueString = QString::number( mMinValue );
629 QString maxValueString = QString::number( mMaxValue );
630 QString nullColorString = mNullColor.name();
631
632 return u"coalesce(ramp_color('%1',scale_linear(%2, %3, %4, 0, 1)), '%5')"_s.arg( !mRampName.isEmpty() ? mRampName : u"custom ramp"_s, baseExpression, minValueString, maxValueString, nullColorString );
633}
634
635QColor QgsColorRampTransformer::color( double value ) const
636{
638 {
639 return mGradientRamp ? mGradientRamp->color( 0 ) : mNullColor;
640 }
641
642 value = transformNumeric( value );
643 double scaledVal = std::clamp( ( value - mMinValue ) / ( mMaxValue - mMinValue ), 0.0, 1.0 );
644
645 if ( !mGradientRamp )
646 return mNullColor;
647
648 return mGradientRamp->color( scaledVal );
649}
650
652{
653 return mGradientRamp.get();
654}
655
657{
658 mGradientRamp.reset( ramp );
659}
660
661
662//
663// QgsCurveTransform
664//
665
666bool sortByX( const QgsPointXY &a, const QgsPointXY &b )
667{
668 return a.x() < b.x();
669}
670
672{
673 mControlPoints << QgsPointXY( 0, 0 ) << QgsPointXY( 1, 1 );
674 calcSecondDerivativeArray();
675}
676
678 : mControlPoints( controlPoints )
679{
680 std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
681 calcSecondDerivativeArray();
682}
683
685{
686 delete[] mSecondDerivativeArray;
687}
688
690 : mControlPoints( other.mControlPoints )
691{
692 if ( other.mSecondDerivativeArray )
693 {
694 mSecondDerivativeArray = new double[mControlPoints.count()];
695 memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
696 }
697}
698
700{
701 if ( this != &other )
702 {
703 mControlPoints = other.mControlPoints;
704 if ( other.mSecondDerivativeArray )
705 {
706 delete[] mSecondDerivativeArray;
707 mSecondDerivativeArray = new double[mControlPoints.count()];
708 memcpy( mSecondDerivativeArray, other.mSecondDerivativeArray, sizeof( double ) * mControlPoints.count() );
709 }
710 }
711 return *this;
712}
713
714void QgsCurveTransform::setControlPoints( const QList<QgsPointXY> &points )
715{
716 mControlPoints = points;
717 std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
718 for ( int i = 0; i < mControlPoints.count(); ++i )
719 {
720 mControlPoints[i] = QgsPointXY( std::clamp( mControlPoints.at( i ).x(), 0.0, 1.0 ), std::clamp( mControlPoints.at( i ).y(), 0.0, 1.0 ) );
721 }
722 calcSecondDerivativeArray();
723}
724
725void QgsCurveTransform::addControlPoint( double x, double y )
726{
727 QgsPointXY point( x, y );
728 if ( mControlPoints.contains( point ) )
729 return;
730
731 mControlPoints << point;
732 std::sort( mControlPoints.begin(), mControlPoints.end(), sortByX );
733 calcSecondDerivativeArray();
734}
735
737{
738 for ( int i = 0; i < mControlPoints.count(); ++i )
739 {
740 if ( qgsDoubleNear( mControlPoints.at( i ).x(), x ) && qgsDoubleNear( mControlPoints.at( i ).y(), y ) )
741 {
742 mControlPoints.removeAt( i );
743 break;
744 }
745 }
746 calcSecondDerivativeArray();
747}
748
749// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
750// which in turn was adapted from
751// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
752
753double QgsCurveTransform::y( double x ) const
754{
755 int n = mControlPoints.count();
756 if ( n < 2 )
757 return std::clamp( x, 0.0, 1.0 ); // invalid
758 else if ( n < 3 )
759 {
760 // linear
761 if ( x <= mControlPoints.at( 0 ).x() )
762 return std::clamp( mControlPoints.at( 0 ).y(), 0.0, 1.0 );
763 else if ( x >= mControlPoints.at( n - 1 ).x() )
764 return std::clamp( mControlPoints.at( 1 ).y(), 0.0, 1.0 );
765 else
766 {
767 double dx = mControlPoints.at( 1 ).x() - mControlPoints.at( 0 ).x();
768 double dy = mControlPoints.at( 1 ).y() - mControlPoints.at( 0 ).y();
769 return std::clamp( ( x - mControlPoints.at( 0 ).x() ) * ( dy / dx ) + mControlPoints.at( 0 ).y(), 0.0, 1.0 );
770 }
771 }
772
773 // safety check
774 if ( x <= mControlPoints.at( 0 ).x() )
775 return std::clamp( mControlPoints.at( 0 ).y(), 0.0, 1.0 );
776 if ( x >= mControlPoints.at( n - 1 ).x() )
777 return std::clamp( mControlPoints.at( n - 1 ).y(), 0.0, 1.0 );
778
779 // find corresponding segment
780 QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
781 QgsPointXY currentControlPoint = *pointIt;
782 ++pointIt;
783 QgsPointXY nextControlPoint = *pointIt;
784
785 for ( int i = 0; i < n - 1; ++i )
786 {
787 if ( x < nextControlPoint.x() )
788 {
789 // found segment
790 double h = nextControlPoint.x() - currentControlPoint.x();
791 double t = ( x - currentControlPoint.x() ) / h;
792
793 double a = 1 - t;
794
795 return std::clamp( a * currentControlPoint.y() + t * nextControlPoint.y() + ( h * h / 6 ) * ( ( a * a * a - a ) * mSecondDerivativeArray[i] + ( t * t * t - t ) * mSecondDerivativeArray[i + 1] ), 0.0, 1.0 );
796 }
797
798 ++pointIt;
799 if ( pointIt == mControlPoints.constEnd() )
800 break;
801
802 currentControlPoint = nextControlPoint;
803 nextControlPoint = *pointIt;
804 }
805
806 //should not happen
807 return std::clamp( x, 0.0, 1.0 );
808}
809
810// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
811// which in turn was adapted from
812// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
813
814QVector<double> QgsCurveTransform::y( const QVector<double> &x ) const
815{
816 QVector<double> result;
817
818 int n = mControlPoints.count();
819 if ( n < 3 )
820 {
821 // invalid control points - use simple transform
822 const auto constX = x;
823 for ( double i : constX )
824 result << y( i );
825
826 return result;
827 }
828
829 // find corresponding segment
830 QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
831 QgsPointXY currentControlPoint = *pointIt;
832 ++pointIt;
833 QgsPointXY nextControlPoint = *pointIt;
834
835 int xIndex = 0;
836 double currentX = x.at( xIndex );
837 // safety check
838 while ( currentX <= currentControlPoint.x() )
839 {
840 result << std::clamp( currentControlPoint.y(), 0.0, 1.0 );
841 xIndex++;
842 currentX = x.at( xIndex );
843 }
844
845 for ( int i = 0; i < n - 1; ++i )
846 {
847 while ( currentX < nextControlPoint.x() )
848 {
849 // found segment
850 double h = nextControlPoint.x() - currentControlPoint.x();
851
852 double t = ( currentX - currentControlPoint.x() ) / h;
853
854 double a = 1 - t;
855
856 result << std::clamp( a * currentControlPoint.y() + t * nextControlPoint.y() + ( h * h / 6 ) * ( ( a * a * a - a ) * mSecondDerivativeArray[i] + ( t * t * t - t ) * mSecondDerivativeArray[i + 1] ), 0.0, 1.0 );
857 xIndex++;
858 if ( xIndex == x.count() )
859 return result;
860
861 currentX = x.at( xIndex );
862 }
863
864 ++pointIt;
865 if ( pointIt == mControlPoints.constEnd() )
866 break;
867
868 currentControlPoint = nextControlPoint;
869 nextControlPoint = *pointIt;
870 }
871
872 // safety check
873 while ( xIndex < x.count() )
874 {
875 result << std::clamp( nextControlPoint.y(), 0.0, 1.0 );
876 xIndex++;
877 }
878
879 return result;
880}
881
882bool QgsCurveTransform::readXml( const QDomElement &elem, const QDomDocument & )
883{
884 QString xString = elem.attribute( u"x"_s );
885 QString yString = elem.attribute( u"y"_s );
886
887 QStringList xVals = xString.split( ',' );
888 QStringList yVals = yString.split( ',' );
889 if ( xVals.count() != yVals.count() )
890 return false;
891
892 QList< QgsPointXY > newPoints;
893 bool ok = false;
894 for ( int i = 0; i < xVals.count(); ++i )
895 {
896 double x = xVals.at( i ).toDouble( &ok );
897 if ( !ok )
898 return false;
899 double y = yVals.at( i ).toDouble( &ok );
900 if ( !ok )
901 return false;
902 newPoints << QgsPointXY( x, y );
903 }
904 setControlPoints( newPoints );
905 return true;
906}
907
908bool QgsCurveTransform::writeXml( QDomElement &transformElem, QDomDocument & ) const
909{
910 QStringList x;
911 QStringList y;
912 const auto constMControlPoints = mControlPoints;
913 for ( const QgsPointXY &p : constMControlPoints )
914 {
915 x << qgsDoubleToString( p.x() );
916 y << qgsDoubleToString( p.y() );
917 }
918
919 transformElem.setAttribute( u"x"_s, x.join( ',' ) );
920 transformElem.setAttribute( u"y"_s, y.join( ',' ) );
921
922 return true;
923}
924
926{
927 QVariantMap transformMap;
928
929 QStringList x;
930 QStringList y;
931 const auto constMControlPoints = mControlPoints;
932 for ( const QgsPointXY &p : constMControlPoints )
933 {
934 x << qgsDoubleToString( p.x() );
935 y << qgsDoubleToString( p.y() );
936 }
937
938 transformMap.insert( u"x"_s, x.join( ',' ) );
939 transformMap.insert( u"y"_s, y.join( ',' ) );
940
941 return transformMap;
942}
943
944bool QgsCurveTransform::loadVariant( const QVariant &transformer )
945{
946 QVariantMap transformMap = transformer.toMap();
947
948 QString xString = transformMap.value( u"x"_s ).toString();
949 QString yString = transformMap.value( u"y"_s ).toString();
950
951 QStringList xVals = xString.split( ',' );
952 QStringList yVals = yString.split( ',' );
953 if ( xVals.count() != yVals.count() )
954 return false;
955
956 QList< QgsPointXY > newPoints;
957 bool ok = false;
958 for ( int i = 0; i < xVals.count(); ++i )
959 {
960 double x = xVals.at( i ).toDouble( &ok );
961 if ( !ok )
962 return false;
963 double y = yVals.at( i ).toDouble( &ok );
964 if ( !ok )
965 return false;
966 newPoints << QgsPointXY( x, y );
967 }
968 setControlPoints( newPoints );
969 return true;
970}
971
972// this code is adapted from https://github.com/OpenFibers/Photoshop-Curves
973// which in turn was adapted from
974// http://www.developpez.net/forums/d331608-3/autres-langages/algorithmes/contribuez/image-interpolation-spline-cubique/#post3513925 //#spellok
975
976void QgsCurveTransform::calcSecondDerivativeArray()
977{
978 int n = mControlPoints.count();
979 if ( n < 3 )
980 return; // cannot proceed
981
982 delete[] mSecondDerivativeArray;
983
984 double *matrix = new double[n * 3];
985 double *result = new double[n];
986 matrix[0] = 0;
987 matrix[1] = 1;
988 matrix[2] = 0;
989 result[0] = 0;
990 QList<QgsPointXY>::const_iterator pointIt = mControlPoints.constBegin();
991 QgsPointXY pointIm1 = *pointIt;
992 ++pointIt;
993 QgsPointXY pointI = *pointIt;
994 ++pointIt;
995 QgsPointXY pointIp1 = *pointIt;
996
997 for ( int i = 1; i < n - 1; ++i )
998 {
999 matrix[i * 3 + 0] = ( pointI.x() - pointIm1.x() ) / 6.0;
1000 matrix[i * 3 + 1] = ( pointIp1.x() - pointIm1.x() ) / 3.0;
1001 matrix[i * 3 + 2] = ( pointIp1.x() - pointI.x() ) / 6.0;
1002 result[i] = ( pointIp1.y() - pointI.y() ) / ( pointIp1.x() - pointI.x() ) - ( pointI.y() - pointIm1.y() ) / ( pointI.x() - pointIm1.x() );
1003
1004 // shuffle points
1005 pointIm1 = pointI;
1006 pointI = pointIp1;
1007 ++pointIt;
1008 if ( pointIt == mControlPoints.constEnd() )
1009 break;
1010
1011 pointIp1 = *pointIt;
1012 }
1013 matrix[( n - 1 ) * 3 + 0] = 0;
1014 matrix[( n - 1 ) * 3 + 1] = 1;
1015 matrix[( n - 1 ) * 3 + 2] = 0;
1016 result[n - 1] = 0;
1017
1018 // solving pass1 (up->down)
1019 for ( int i = 1; i < n; ++i )
1020 {
1021 double k = matrix[i * 3 + 0] / matrix[( i - 1 ) * 3 + 1];
1022 matrix[i * 3 + 1] -= k * matrix[( i - 1 ) * 3 + 2];
1023 matrix[i * 3 + 0] = 0;
1024 result[i] -= k * result[i - 1];
1025 }
1026 // solving pass2 (down->up)
1027 for ( int i = n - 2; i >= 0; --i )
1028 {
1029 double k = matrix[i * 3 + 2] / matrix[( i + 1 ) * 3 + 1];
1030 matrix[i * 3 + 1] -= k * matrix[( i + 1 ) * 3 + 0];
1031 matrix[i * 3 + 2] = 0;
1032 result[i] -= k * result[i + 1];
1033 }
1034
1035 // return second derivative value for each point
1036 mSecondDerivativeArray = new double[n];
1037 for ( int i = 0; i < n; ++i )
1038 {
1039 mSecondDerivativeArray[i] = result[i] / matrix[( i * 3 ) + 1];
1040 }
1041
1042 delete[] result;
1043 delete[] matrix;
1044}
QgsPropertyTransformer subclass for transforming a numeric value into a color from a color ramp.
QgsColorRamp * colorRamp() const
Returns the color ramp used for calculating property colors.
QString rampName() const
Returns the color ramp's name.
QgsColorRampTransformer * clone() const override
Returns a clone of the transformer.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
QColor color(double value) const
Calculates the color corresponding to a specific value.
QColor nullColor() const
Returns the color corresponding to a null value.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsColorRampTransformer & operator=(const QgsColorRampTransformer &other)
QgsColorRampTransformer(double minValue=0.0, double maxValue=1.0, QgsColorRamp *ramp=nullptr, const QColor &nullColor=QColor(0, 0, 0, 0), const QString &rampName=QString())
Constructor for QgsColorRampTransformer.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp to use for calculating property colors.
Abstract base class for color ramps.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Handles scaling of input values to output values by using a curve created from smoothly joining a num...
QgsCurveTransform & operator=(const QgsCurveTransform &other)
bool readXml(const QDomElement &elem, const QDomDocument &doc)
Reads the curve's state from an XML element.
void setControlPoints(const QList< QgsPointXY > &points)
Sets the list of control points for the transform.
QVariant toVariant() const
Saves this curve transformer to a QVariantMap, wrapped in a QVariant.
bool writeXml(QDomElement &transformElem, QDomDocument &doc) const
Writes the current state of the transform into an XML element.
QgsCurveTransform()
Constructs a default QgsCurveTransform which linearly maps values between 0 and 1 unchanged.
void removeControlPoint(double x, double y)
Removes a control point from the transform.
void addControlPoint(double x, double y)
Adds a control point to the transform.
double y(double x) const
Returns the mapped y value corresponding to the specified x value.
bool loadVariant(const QVariant &transformer)
Load this curve transformer from a QVariantMap, wrapped in a QVariant.
QList< QgsPointXY > controlPoints() const
Returns a list of the control points for the transform.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
An expression node which takes its value from a feature's field.
An expression node for expression functions.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
std::shared_ptr< QgsExpressionFunction > function() const
Returns a reference to the node's function.
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
Handles parsing and evaluation of expressions (formerly called "search strings").
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required.
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
std::shared_ptr< QgsExpressionFunction > SharedFunctionPtr
QVariant evaluate()
Evaluate the feature and return the result.
QgsPropertyTransformer subclass for scaling an input numeric value into an output numeric value.
double value(double input) const
Calculates the size corresponding to a specific input value.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
double exponent() const
Returns the exponent for an exponential expression.
QgsGenericNumericTransformer * clone() const override
Returns a clone of the transformer.
QgsGenericNumericTransformer(double minValue=0.0, double maxValue=1.0, double minOutput=0.0, double maxOutput=1.0, double nullOutput=0.0, double exponent=1.0)
Constructor for QgsGenericNumericTransformer.
static QgsGenericNumericTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
static QgsPropertyTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding property transformer.
virtual bool loadVariant(const QVariant &transformer)
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsPropertyTransformer & operator=(const QgsPropertyTransformer &other)
virtual QVariant toVariant() const
Saves this transformer to a QVariantMap, wrapped in a QVariant.
double mMinValue
Minimum value expected by the transformer.
double maxValue() const
Returns the maximum value expected by the transformer.
std::unique_ptr< QgsCurveTransform > mCurveTransform
Optional curve transform.
QgsPropertyTransformer(double minValue=0.0, double maxValue=1.0)
Constructor for QgsPropertyTransformer.
virtual ~QgsPropertyTransformer()
@ GenericNumericTransformer
Generic transformer for numeric values (QgsGenericNumericTransformer).
@ SizeScaleTransformer
Size scaling transformer (QgsSizeScaleTransformer).
@ ColorRampTransformer
Color ramp transformer (QgsColorRampTransformer).
double transformNumeric(double input) const
Applies base class numeric transformations.
static QgsPropertyTransformer * create(Type type)
Factory method for creating a new property transformer of the specified type.
double minValue() const
Returns the minimum value expected by the transformer.
double mMaxValue
Maximum value expected by the transformer.
QgsPropertyTransformer subclass for scaling a value into a size according to various scaling methods.
bool loadVariant(const QVariant &definition) override
Loads this transformer from a QVariantMap, wrapped in a QVariant.
QgsSizeScaleTransformer * clone() const override
Returns a clone of the transformer.
static QgsSizeScaleTransformer * fromExpression(const QString &expression, QString &baseExpression, QString &fieldName)
Attempts to parse an expression into a corresponding QgsSizeScaleTransformer.
ScaleType type() const
Returns the size transformer's scaling type (the method used to calculate the size from a value).
double maxSize() const
Returns the maximum calculated size.
QVariant transform(const QgsExpressionContext &context, const QVariant &value) const override
Calculates the transform of a value.
double nullSize() const
Returns the size value when an expression evaluates to NULL.
double minSize() const
Returns the minimum calculated size.
double size(double value) const
Calculates the size corresponding to a specific value.
@ Exponential
Scale using set exponent.
@ Flannery
Flannery scaling method.
QString toExpression(const QString &baseExpression) const override
Converts the transformer to a QGIS expression string.
void setType(ScaleType type)
Sets the size transformer's scaling type (the method used to calculate the size from a value).
double exponent() const
Returns the exponent for an exponential expression.
QVariant toVariant() const override
Saves this transformer to a QVariantMap, wrapped in a QVariant.
QgsSizeScaleTransformer(ScaleType type=Linear, double minValue=0.0, double maxValue=1.0, double minSize=0.0, double maxSize=1.0, double nullSize=0.0, double exponent=1.0)
Constructor for QgsSizeScaleTransformer.
static QVariant colorRampToVariant(const QString &name, QgsColorRamp *ramp)
Saves a color ramp to a QVariantMap, wrapped in a QVariant.
static std::unique_ptr< QgsColorRamp > loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7650
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7743
bool sortByX(const QgsPointXY &a, const QgsPointXY &b)