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