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