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