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