QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsinterpolatedlinerenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsinterpolatedlinerenderer.cpp
3  --------------------------------------
4  Date : April 2020
5  Copyright : (C) 2020 by Vincent Cloarec
6  Email : vcloarec 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 <QPainter>
17 
19 #include "qgscolorramplegendnode.h"
20 #include "qgssymbollayerutils.h"
21 #include "qgsstyle.h"
22 
23 
25 {
26  mStrokeWidth = strokeWidth;
27 }
28 
30 {
31  return mStrokeWidth;
32 }
33 
35 {
36  mStrokeColoring = strokeColoring;
37 }
38 
40 {
41  return mStrokeColoring;
42 }
43 
45 {
46  mStrokeWidthUnit = strokeWidthUnit;
47 }
48 
50 {
51  return mStrokeWidthUnit;
52 }
53 
54 void QgsInterpolatedLineRenderer::renderInDeviceCoordinate( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QPointF &p1, const QPointF &p2, QgsRenderContext &context ) const
55 {
56  QPainter *painter = context.painter();
57  QgsScopedQPainterState painterState( painter );
58  context.setPainterFlagsUsingContext( painter );
59 
60  QPointF dir = p2 - p1;
61  double length = sqrt( pow( dir.x(), 2 ) + pow( dir.y(), 2 ) );
62  QPointF diru = dir / length;
63  QPointF orthu = QPointF( -diru.y(), diru.x() );
64 
65  QList<double> breakValues;
66  QList<QColor> breakColors;
67  QList<QLinearGradient> gradients;
68 
69  mStrokeColoring.graduatedColors( valueColor1, valueColor2, breakValues, breakColors, gradients );
70  QColor selectedColor = context.selectionColor();
71 
72  if ( gradients.isEmpty() && !breakValues.empty() && !breakColors.isEmpty() ) //exact colors to render
73  {
74  Q_ASSERT( breakColors.count() == breakValues.count() );
75  for ( int i = 0; i < breakValues.count(); ++i )
76  {
77  double value = breakValues.at( i );
78  double width = context.convertToPainterUnits( mStrokeWidth.strokeWidth( value ), mStrokeWidthUnit );
79  QPen pen( mSelected ? selectedColor : breakColors.at( i ) );
80  pen.setWidthF( width );
81  pen.setCapStyle( Qt::PenCapStyle::RoundCap );
82  painter->setPen( pen );
83  QPointF point = p1 + dir * ( value - valueColor1 ) / ( valueColor2 - valueColor1 );
84  painter->drawPoint( point );
85  }
86  }
87  else
88  {
89  double width1 = mStrokeWidth.strokeWidth( valueWidth1 );
90  double width2 = mStrokeWidth.strokeWidth( valueWidth2 );
91 
92  if ( !std::isnan( width1 ) || !std::isnan( width2 ) ) // the two widths on extremity are not out of range and ignored
93  {
94  //Draw line cap
95  QBrush brush( Qt::SolidPattern );
96  QPen pen;
97  int startAngle;
98  startAngle = ( acos( -orthu.x() ) / M_PI ) * 180;
99  if ( orthu.y() < 0 )
100  startAngle = 360 - startAngle;
101 
102  bool outOfRange1 = std::isnan( width1 );
103  bool outOfRange2 = std::isnan( width2 );
104 
105  if ( !outOfRange1 )
106  {
107  width1 = context.convertToPainterUnits( width1, mStrokeWidthUnit );
108  QRectF capBox1( p1.x() - width1 / 2, p1.y() - width1 / 2, width1, width1 );
109  brush.setColor( mSelected ? selectedColor : mStrokeColoring.color( valueColor1 ) );
110  painter->setBrush( brush );
111  pen.setBrush( brush );
112  painter->setPen( pen );
113  painter->drawPie( capBox1, ( startAngle - 1 ) * 16, 182 * 16 );
114  }
115 
116  if ( !outOfRange2 )
117  {
118  width2 = context.convertToPainterUnits( width2, mStrokeWidthUnit ) ;
119  QRectF capBox2( p2.x() - width2 / 2, p2.y() - width2 / 2, width2, width2 );
120  brush.setColor( mSelected ? selectedColor : mStrokeColoring.color( valueColor2 ) );
121  pen.setBrush( brush );
122  painter->setBrush( brush );
123  painter->setPen( pen );
124  painter->drawPie( capBox2, ( startAngle + 179 ) * 16, 182 * 16 );
125  }
126 
127  if ( ( gradients.isEmpty() && breakValues.empty() && breakColors.count() == 1 ) || mSelected ) //only one color to render
128  {
129  double startAdjusting = 0;
130  if ( outOfRange1 )
131  adjustLine( valueColor1, valueColor1, valueColor2, width1, startAdjusting );
132 
133 
134  double endAdjusting = 0;
135  if ( outOfRange2 )
136  adjustLine( valueColor2, valueColor1, valueColor2, width2, endAdjusting );
137 
138  QPointF pointStartAdjusted = p1 + dir * startAdjusting;
139  QPointF pointEndAdjusted = p2 - dir * endAdjusting;
140 
141  QPolygonF varLine;
142  double semiWidth1 = width1 / 2;
143  double semiWidth2 = width2 / 2;
144 
145  varLine.append( pointStartAdjusted + orthu * semiWidth1 );
146  varLine.append( pointEndAdjusted + orthu * semiWidth2 );
147  varLine.append( pointEndAdjusted - orthu * semiWidth2 );
148  varLine.append( pointStartAdjusted - orthu * semiWidth1 );
149 
150  QBrush brush( Qt::SolidPattern );
151  brush.setColor( mSelected ? selectedColor : breakColors.first() );
152  painter->setBrush( brush );
153  painter->setPen( pen );
154 
155  QPen pen;
156  pen.setBrush( brush );
157  pen.setWidthF( 0 );
158  painter->setPen( pen );
159 
160  painter->drawPolygon( varLine );
161  }
162  else if ( !gradients.isEmpty() && !breakValues.isEmpty() && !breakColors.isEmpty() )
163  {
164  Q_ASSERT( breakColors.count() == breakValues.count() );
165  Q_ASSERT( breakColors.count() == gradients.count() + 1 );
166  double widthColorVariationValueRatio = ( valueWidth2 - valueWidth1 ) / ( valueColor2 - valueColor1 );
167 
168  for ( int i = 0; i < gradients.count(); ++i )
169  {
170  double firstValue = breakValues.at( i );
171  double secondValue = breakValues.at( i + 1 );
172  double w1 = mStrokeWidth.strokeWidth( widthColorVariationValueRatio * ( firstValue - valueColor1 ) + valueWidth1 );
173  double w2 = mStrokeWidth.strokeWidth( widthColorVariationValueRatio * ( secondValue - valueColor1 ) + valueWidth1 );
174 
175  if ( std::isnan( w1 ) && std::isnan( w2 ) )
176  continue;
177 
178  double firstAdjusting = 0;
179  if ( std::isnan( w1 ) )
180  adjustLine( firstValue, valueColor1, valueColor2, w1, firstAdjusting );
181 
182 
183  double secondAdjusting = 0;
184  if ( std::isnan( w2 ) )
185  adjustLine( secondValue, valueColor1, valueColor2, w2, secondAdjusting );
186 
187  w1 = context.convertToPainterUnits( w1, mStrokeWidthUnit );
188  w2 = context.convertToPainterUnits( w2, mStrokeWidthUnit ) ;
189 
190  QPointF pointStart = p1 + dir * ( firstValue - valueColor1 ) / ( valueColor2 - valueColor1 );
191  QPointF pointEnd = p1 + dir * ( secondValue - valueColor1 ) / ( valueColor2 - valueColor1 );
192 
193  QPointF pointStartAdjusted = pointStart + dir * firstAdjusting;
194  QPointF pointEndAdjusted = pointEnd - dir * secondAdjusting;
195 
196  QPolygonF varLine;
197  double sw1 = w1 / 2;
198  double sw2 = w2 / 2;
199 
200  varLine.append( pointStartAdjusted + orthu * sw1 );
201  varLine.append( pointEndAdjusted + orthu * sw2 );
202  varLine.append( pointEndAdjusted - orthu * sw2 );
203  varLine.append( pointStartAdjusted - orthu * sw1 );
204 
205  QLinearGradient gradient = gradients.at( i );
206  gradient.setStart( pointStart );
207  gradient.setFinalStop( pointEnd );
208  QBrush brush( gradient );
209  painter->setBrush( brush );
210 
211  QPen pen;
212  pen.setBrush( brush );
213  pen.setWidthF( 0 );
214  painter->setPen( pen );
215 
216  painter->drawPolygon( varLine );
217  }
218  }
219  }
220  }
221 }
222 
223 
224 void QgsInterpolatedLineRenderer::render( double value1, double value2, const QgsPointXY &pt1, const QgsPointXY &pt2, QgsRenderContext &context ) const
225 {
226  const QgsMapToPixel &mapToPixel = context.mapToPixel();
227 
228  QgsPointXY point1 = pt1;
229  QgsPointXY point2 = pt2;
230 
231  if ( value1 > value2 )
232  {
233  std::swap( value1, value2 );
234  std::swap( point1, point2 );
235  }
236 
237  QPointF p1 = mapToPixel.transform( point1 ).toQPointF();
238  QPointF p2 = mapToPixel.transform( point2 ).toQPointF();
239 
240  renderInDeviceCoordinate( value1, value2, value1, value2, p1, p2, context );
241 }
242 
243 void QgsInterpolatedLineRenderer::render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &pt1, const QgsPointXY &pt2, QgsRenderContext &context ) const
244 {
245  const QgsMapToPixel &mapToPixel = context.mapToPixel();
246 
247  QgsPointXY point1 = pt1;
248  QgsPointXY point2 = pt2;
249 
250  if ( valueColor1 > valueColor2 )
251  {
252  std::swap( valueColor1, valueColor2 );
253  std::swap( valueWidth1, valueWidth2 );
254  std::swap( point1, point2 );
255  }
256 
257  QPointF p1 = mapToPixel.transform( point1 ).toQPointF();
258  QPointF p2 = mapToPixel.transform( point2 ).toQPointF();
259 
260  renderInDeviceCoordinate( valueColor1, valueColor2, valueWidth1, valueWidth2, p1, p2, context );
261 }
262 
264 {
265  mSelected = selected;
266 }
267 
268 void QgsInterpolatedLineRenderer::adjustLine( const double &value, const double &value1, const double &value2, double &width, double &adjusting ) const
269 {
270  if ( value > mStrokeWidth.maximumValue() )
271  {
272  adjusting = fabs( ( value - mStrokeWidth.maximumValue() ) / ( value2 - value1 ) );
273  width = mStrokeWidth.maximumWidth();
274  }
275  else
276  {
277  adjusting = fabs( ( value - mStrokeWidth.minimumValue() ) / ( value2 - value1 ) );
278  width = mStrokeWidth.minimumWidth();
279  }
280 }
281 
283 {
284  return mMinimumValue;
285 }
286 
287 void QgsInterpolatedLineWidth::setMinimumValue( double minimumValue )
288 {
289  mMinimumValue = minimumValue;
290  mNeedUpdateFormula = true;
291 }
292 
294 {
295  return mMaximumValue;
296 }
297 
298 void QgsInterpolatedLineWidth::setMaximumValue( double maximumValue )
299 {
300  mMaximumValue = maximumValue;
301  mNeedUpdateFormula = true;
302 }
303 
305 {
306  return mMinimumWidth;
307 }
308 
309 void QgsInterpolatedLineWidth::setMinimumWidth( double minimumWidth )
310 {
311  mMinimumWidth = minimumWidth;
312  mNeedUpdateFormula = true;
313 }
314 
316 {
317  return mMaximumWidth;
318 }
319 
320 void QgsInterpolatedLineWidth::setMaximumWidth( double maximumWidth )
321 {
322  mMaximumWidth = maximumWidth;
323  mNeedUpdateFormula = true;
324 }
325 
326 double QgsInterpolatedLineWidth::strokeWidth( double value ) const
327 {
328  if ( mIsWidthVariable )
329  {
330  if ( mNeedUpdateFormula )
331  updateLinearFormula();
332 
333  if ( mUseAbsoluteValue )
334  value = std::fabs( value );
335 
336  if ( value > mMaximumValue )
337  {
338  if ( mIgnoreOutOfRange )
339  return std::numeric_limits<double>::quiet_NaN();
340  else
341  return mMaximumWidth;
342  }
343 
344  if ( value < mMinimumValue )
345  {
346  if ( mIgnoreOutOfRange )
347  return std::numeric_limits<double>::quiet_NaN();
348  else
349  return mMinimumWidth;
350  }
351 
352  return ( value - mMinimumValue ) * mLinearCoef + mMinimumWidth;
353  }
354  else
355  return fixedStrokeWidth();
356 }
357 
358 QDomElement QgsInterpolatedLineWidth::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
359 {
360  Q_UNUSED( context );
361 
362  QDomElement elem = doc.createElement( QStringLiteral( "mesh-stroke-width" ) );
363 
364  elem.setAttribute( QStringLiteral( "width-varying" ), mIsWidthVariable ? 1 : 0 );
365  elem.setAttribute( QStringLiteral( "fixed-width" ), mFixedWidth );
366  elem.setAttribute( QStringLiteral( "minimum-value" ), mMinimumValue );
367  elem.setAttribute( QStringLiteral( "maximum-value" ), mMaximumValue );
368  elem.setAttribute( QStringLiteral( "minimum-width" ), mMinimumWidth );
369  elem.setAttribute( QStringLiteral( "maximum-width" ), mMaximumWidth );
370  elem.setAttribute( QStringLiteral( "ignore-out-of-range" ), mIgnoreOutOfRange ? 1 : 0 );
371  elem.setAttribute( QStringLiteral( "use-absolute-value" ), mUseAbsoluteValue ? 1 : 0 );
372 
373  return elem;
374 }
375 
376 void QgsInterpolatedLineWidth::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
377 {
378  Q_UNUSED( context );
379 
380  mIsWidthVariable = elem.attribute( QStringLiteral( "width-varying" ) ).toInt();
381  mFixedWidth = elem.attribute( QStringLiteral( "fixed-width" ) ).toDouble();
382  mMinimumValue = elem.attribute( QStringLiteral( "minimum-value" ) ).toDouble();
383  mMaximumValue = elem.attribute( QStringLiteral( "maximum-value" ) ).toDouble();
384  mMinimumWidth = elem.attribute( QStringLiteral( "minimum-width" ) ).toDouble();
385  mMaximumWidth = elem.attribute( QStringLiteral( "maximum-width" ) ).toDouble();
386  mIgnoreOutOfRange = elem.attribute( QStringLiteral( "ignore-out-of-range" ) ).toInt();
387  mUseAbsoluteValue = elem.attribute( QStringLiteral( "use-absolute-value" ) ).toInt();
388 }
389 
391 {
392  return mUseAbsoluteValue;
393 }
394 
396 {
397  mUseAbsoluteValue = useAbsoluteValue;
398 }
399 
401 {
402  return mFixedWidth;
403 }
404 
406 {
407  return mIgnoreOutOfRange;
408 }
409 
411 {
412  mIgnoreOutOfRange = ignoreOutOfRange;
413 }
414 
416 {
417  return mIsWidthVariable;
418 }
419 
421 {
422  mIsWidthVariable = isWidthVarying;
423 }
424 
426 {
427  mFixedWidth = fixedWidth;
428 }
429 
430 void QgsInterpolatedLineWidth::updateLinearFormula() const
431 {
432  if ( mMaximumWidth - mMinimumWidth != 0 )
433  mLinearCoef = ( mMaximumWidth - mMinimumWidth ) / ( mMaximumValue - mMinimumValue ) ;
434  else
435  mLinearCoef = 0;
436  mNeedUpdateFormula = false;
437 }
438 
440 {
441  mColorRampShader.setMinimumValue( std::numeric_limits<double>::quiet_NaN() );
442  mColorRampShader.setMaximumValue( std::numeric_limits<double>::quiet_NaN() );
443 }
444 
446 {
448 }
449 
451 {
452  setColor( color );
453  mColoringMethod = SingleColor;
454  mColorRampShader.setMinimumValue( std::numeric_limits<double>::quiet_NaN() );
455  mColorRampShader.setMaximumValue( std::numeric_limits<double>::quiet_NaN() );
456 }
457 
459 {
460  mColorRampShader = colorRampShader;
461  if ( ( mColorRampShader.sourceColorRamp() ) )
462  mColoringMethod = ColorRamp;
463  else
464  mColoringMethod = SingleColor;
465 }
466 
467 void QgsInterpolatedLineColor::setColor( const QColor &color )
468 {
469  mSingleColor = color;
470 }
471 
472 QColor QgsInterpolatedLineColor::color( double magnitude ) const
473 {
474  QgsColorRamp *lSourceColorRamp = mColorRampShader.sourceColorRamp();
475  if ( mColoringMethod == ColorRamp && lSourceColorRamp )
476  {
477  if ( mColorRampShader.isEmpty() )
478  return lSourceColorRamp->color( 0 );
479 
480  int r, g, b, a;
481  if ( mColorRampShader.shade( magnitude, &r, &g, &b, &a ) )
482  return QColor( r, g, b, a );
483  else
484  return QColor( 0, 0, 0, 0 );
485  }
486  else
487  {
488  return mSingleColor;
489  }
490 }
491 
493 {
494  return mColoringMethod;
495 }
496 
498 {
499  return mColorRampShader;
500 }
501 
503 {
504  return mSingleColor;
505 }
506 
507 QDomElement QgsInterpolatedLineColor::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
508 {
509  Q_UNUSED( context );
510 
511  QDomElement elem = doc.createElement( QStringLiteral( "mesh-stroke-color" ) );
512 
513  elem.setAttribute( QStringLiteral( "single-color" ), QgsSymbolLayerUtils::encodeColor( mSingleColor ) );
514  elem.setAttribute( QStringLiteral( "coloring-method" ), mColoringMethod );
515  elem.appendChild( mColorRampShader.writeXml( doc ) );
516 
517  return elem;
518 }
519 
520 void QgsInterpolatedLineColor::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
521 {
522  Q_UNUSED( context );
523 
524  QDomElement shaderElem = elem.firstChildElement( QStringLiteral( "colorrampshader" ) );
525  mColorRampShader.readXml( shaderElem );
526 
527  mSingleColor = QgsSymbolLayerUtils::decodeColor( elem.attribute( QStringLiteral( "single-color" ) ) );
528  mColoringMethod = static_cast<QgsInterpolatedLineColor::ColoringMethod>(
529  elem.attribute( QStringLiteral( "coloring-method" ) ).toInt() );
530 }
531 
532 void QgsInterpolatedLineColor::graduatedColors( double value1, double value2, QList<double> &breakValues, QList<QColor> &breakColors, QList<QLinearGradient> &gradients ) const
533 {
534  breakValues.clear();
535  breakColors.clear();
536  gradients.clear();
537  if ( mColoringMethod == SingleColor )
538  {
539  breakColors.append( mSingleColor );
540  return;
541  }
542 
543  switch ( mColorRampShader.colorRampType() )
544  {
546  graduatedColorsInterpolated( value1, value2, breakValues, breakColors, gradients );
547  break;
549  graduatedColorsDiscrete( value1, value2, breakValues, breakColors, gradients );
550  break;
552  graduatedColorsExact( value1, value2, breakValues, breakColors, gradients );
553  break;
554  }
555 
556 }
557 
559 {
560  mColoringMethod = coloringMethod;
561 }
562 
563 QLinearGradient QgsInterpolatedLineColor::makeSimpleLinearGradient( const QColor &color1, const QColor &color2 ) const
564 {
565  QLinearGradient gradient;
566  gradient.setColorAt( 0, color1 );
567  gradient.setColorAt( 1, color2 );
568 
569  return gradient;
570 }
571 
572 int QgsInterpolatedLineColor::itemColorIndexInf( double value ) const
573 {
574  QList<QgsColorRampShader::ColorRampItem> itemList = mColorRampShader.colorRampItemList();
575 
576  if ( itemList.isEmpty() || itemList.first().value > value )
577  return -1;
578 
579  if ( mColorRampShader.colorRampType() == QgsColorRampShader::Discrete )
580  itemList.removeLast(); //remove the inf value
581 
582  if ( value > itemList.last().value )
583  return itemList.count() - 1;
584 
585  int indSup = itemList.count() - 1;
586  int indInf = 0;
587 
588  while ( true )
589  {
590  if ( abs( indSup - indInf ) <= 1 ) //always indSup>indInf, but abs to prevent infinity loop
591  return indInf;
592 
593  int newInd = ( indInf + indSup ) / 2;
594 
595  if ( itemList.at( newInd ).value == std::numeric_limits<double>::quiet_NaN() )
596  return -1;
597 
598  if ( itemList.at( newInd ).value <= value )
599  indInf = newInd;
600  else
601  indSup = newInd;
602  }
603 }
604 
605 void QgsInterpolatedLineColor::graduatedColorsExact( double value1, double value2, QList<double> &breakValues, QList<QColor> &breakColors, QList<QLinearGradient> &gradients ) const
606 {
607  Q_ASSERT( mColorRampShader.colorRampType() == QgsColorRampShader::Exact );
608  Q_ASSERT( breakValues.isEmpty() );
609  Q_ASSERT( breakColors.isEmpty() );
610  Q_ASSERT( gradients.isEmpty() );
611 
612  const QList<QgsColorRampShader::ColorRampItem> &itemList = mColorRampShader.colorRampItemList();
613  if ( itemList.isEmpty() )
614  return;
615 
616  int index = itemColorIndexInf( value1 );
617  if ( index < 0 || !qgsDoubleNear( value1, itemList.at( index ).value ) )
618  index++;
619 
620  if ( qgsDoubleNear( value1, value2 ) && qgsDoubleNear( value1, itemList.at( index ).value ) )
621  {
622  //the two value are the same and are equal to the value in the item list --> render only one color
623  breakColors.append( itemList.at( index ).color );
624  return;
625  }
626 
627  while ( index < itemList.count() && itemList.at( index ).value <= value2 )
628  {
629  breakValues.append( itemList.at( index ).value );
630  breakColors.append( itemList.at( index ).color );
631  index++;
632  }
633 }
634 
635 void QgsInterpolatedLineColor::graduatedColorsInterpolated( double value1, double value2, QList<double> &breakValues, QList<QColor> &breakColors, QList<QLinearGradient> &gradients ) const
636 {
637  Q_ASSERT( mColorRampShader.colorRampType() == QgsColorRampShader::Interpolated );
638  Q_ASSERT( breakValues.isEmpty() );
639  Q_ASSERT( breakColors.isEmpty() );
640  Q_ASSERT( gradients.isEmpty() );
641 
642 
643  const QList<QgsColorRampShader::ColorRampItem> &itemList = mColorRampShader.colorRampItemList();
644  if ( itemList.empty() )
645  return;
646 
647  if ( itemList.count() == 1 )
648  {
649  breakColors.append( itemList.first().color );
650  return;
651  }
652 
653  if ( value2 <= itemList.first().value ) // completely out of range and less
654  {
655  if ( !mColorRampShader.clip() )
656  breakColors.append( itemList.first().color ); // render only the first color in the whole range if not clipped
657  return;
658  }
659 
660  if ( value1 > itemList.last().value ) // completely out of range and greater
661  {
662  if ( !mColorRampShader.clip() )
663  breakColors.append( itemList.last().color ); // render only the last color in the whole range if not clipped
664  return;
665  }
666 
667  if ( qgsDoubleNear( value1, value2 ) )
668  {
669  // the two values are the same
670  // --> render only one color
671  int r, g, b, a;
672  QColor color;
673  if ( mColorRampShader.shade( value1, &r, &g, &b, &a ) )
674  color = QColor( r, g, b, a );
675  breakColors.append( color );
676  return;
677  }
678 
679  // index of the inf value of the interval where value1 is in the color ramp shader
680  int index = itemColorIndexInf( value1 );
681  if ( index < 0 ) // value1 out of range
682  {
683  QColor color = itemList.first().color;
684  breakColors.append( color );
685  if ( mColorRampShader.clip() ) // The first value/color returned is the first of the item list
686  breakValues.append( itemList.first().value );
687  else // The first value/color returned is the first color of the item list and value1
688  breakValues.append( value1 );
689  }
690  else
691  {
692  // shade the color
693  int r, g, b, a;
694  QColor color;
695  if ( mColorRampShader.shade( value1, &r, &g, &b, &a ) )
696  color = QColor( r, g, b, a );
697  breakValues.append( value1 );
698  breakColors.append( color );
699  }
700 
701  index++; // increment the index before go through the intervals
702 
703  while ( index < itemList.count() && itemList.at( index ).value < value2 )
704  {
705  QColor color1 = breakColors.last();
706  QColor color2 = itemList.at( index ).color;
707  breakValues.append( itemList.at( index ).value );
708  breakColors.append( color2 );
709  gradients.append( makeSimpleLinearGradient( color1, color2 ) );
710  index++;
711  }
712 
713  // close the lists with value2 or last item if >value2
714  QColor color1 = breakColors.last();
715  QColor color2;
716  if ( value2 < itemList.last().value )
717  {
718  int r, g, b, a;
719  if ( mColorRampShader.shade( value2, &r, &g, &b, &a ) )
720  color2 = QColor( r, g, b, a );
721  breakValues.append( value2 );
722  }
723  else
724  {
725  color2 = itemList.last().color;
726  if ( mColorRampShader.clip() )
727  breakValues.append( itemList.last().value );
728  else
729  breakValues.append( value2 );
730  }
731  breakColors.append( color2 );
732  gradients.append( makeSimpleLinearGradient( color1, color2 ) );
733 }
734 
735 
736 void QgsInterpolatedLineColor::graduatedColorsDiscrete( double value1, double value2, QList<double> &breakValues, QList<QColor> &breakColors, QList<QLinearGradient> &gradients ) const
737 {
738  Q_ASSERT( mColorRampShader.colorRampType() == QgsColorRampShader::Discrete );
739  Q_ASSERT( breakValues.isEmpty() );
740  Q_ASSERT( breakColors.isEmpty() );
741  Q_ASSERT( gradients.isEmpty() );
742 
743  const QList<QgsColorRampShader::ColorRampItem> &itemList = mColorRampShader.colorRampItemList();
744  if ( itemList.empty() )
745  return;
746 
747  if ( itemList.count() == 1 )
748  {
749  breakColors.append( itemList.first().color );
750  return;
751  }
752 
753  double lastValue = itemList.at( itemList.count() - 2 ).value;
754 
755 
756  if ( value2 <= itemList.first().value ) // completely out of range and less
757  {
758  breakColors.append( itemList.first().color ); // render only the first color in the whole range
759  return;
760  }
761 
762  if ( value1 > lastValue ) // completely out of range and greater
763  {
764  breakColors.append( itemList.last().color ); // render only the last color in the whole range
765  return;
766  }
767 
768  // index of the inf value of the interval where value1 is in the color ramp shader
769  int index = itemColorIndexInf( value1 );
770 
771  if ( qgsDoubleNear( value1, value2 ) )
772  {
773  // the two values are the same and are equal to the value in the item list
774  // --> render only one color, the sup one
775  breakColors.append( itemList.at( index + 1 ).color );
776  return;
777  }
778 
779  if ( index < 0 ) // value1 out of range
780  {
781  breakValues.append( value1 );
782  breakColors.append( itemList.first().color );
783  }
784  else // append the first value with corresponding color
785  {
786  QColor color = itemList.at( index ).color;
787  breakValues.append( value1 );
788  breakColors.append( color );
789  }
790 
791  index++; // increment the index before go through the intervals
792 
793  while ( index < ( itemList.count() - 1 ) && itemList.at( index ).value < value2 )
794  {
795  QColor color = itemList.at( index ).color;
796  breakValues.append( itemList.at( index ).value );
797  breakColors.append( color );
798  gradients.append( makeSimpleLinearGradient( color, color ) );
799  index++;
800  }
801 
802  // add value2 to close
803  QColor lastColor = itemList.at( index ).color;
804  breakColors.append( lastColor );
805  breakValues.append( value2 );
806  gradients.append( makeSimpleLinearGradient( lastColor, lastColor ) );
807 
808 }
809 
810 QString QgsInterpolatedLineSymbolLayer::layerType() const {return QStringLiteral( "InterpolatedLine" );}
811 
813 {
814  // find out attribute index from name
815  mStartWidthAttributeIndex = mFields.lookupField( mStartWidthExpressionString );
816  mEndWidthAttributeIndex = mFields.lookupField( mEndWidthExpressionString );
817  mStartColorAttributeIndex = mFields.lookupField( mStartColorExpressionString );
818  mEndColorAttributeIndex = mFields.lookupField( mEndColorExpressionString );
819 
820  if ( mStartWidthAttributeIndex == -1 )
821  {
822  mStartWidthExpression.reset( new QgsExpression( mStartWidthExpressionString ) );
823  mStartWidthExpression->prepare( &context.renderContext().expressionContext() );
824  }
825 
826  if ( mEndWidthAttributeIndex == -1 )
827  {
828  mEndWithExpression.reset( new QgsExpression( mEndWidthExpressionString ) );
829  mEndWithExpression->prepare( &context.renderContext().expressionContext() );
830  }
831 
832  if ( mStartColorAttributeIndex == -1 )
833  {
834  mStartColorExpression.reset( new QgsExpression( mStartColorExpressionString ) );
835  mStartColorExpression->prepare( &context.renderContext().expressionContext() );
836  }
837 
838  if ( mEndColorAttributeIndex == -1 )
839  {
840  mEndColorExpression.reset( new QgsExpression( mEndColorExpressionString ) );
841  mEndColorExpression->prepare( &context.renderContext().expressionContext() );
842  }
843 }
844 
846 {
847  mStartWidthExpression.reset();
848  mEndWithExpression.reset();
849  mStartColorExpression.reset();
850  mEndColorExpression.reset();
851 }
852 
854 {
857  copyPaintEffect( l );
858  return l;
859 }
860 
862 {
863  std::unique_ptr<QgsInterpolatedLineSymbolLayer> symbolLayer;
864  symbolLayer.reset( new QgsInterpolatedLineSymbolLayer() );
865 
866  if ( properties.contains( QStringLiteral( "start_width_expression" ) ) )
867  symbolLayer->mStartWidthExpressionString = properties.value( QStringLiteral( "start_width_expression" ) ).toString();
868  if ( properties.contains( QStringLiteral( "end_width_expression" ) ) )
869  symbolLayer->mEndWidthExpressionString = properties.value( QStringLiteral( "end_width_expression" ) ).toString();
870 
871  if ( properties.contains( QStringLiteral( "start_color_expression" ) ) )
872  symbolLayer->mStartColorExpressionString = properties.value( QStringLiteral( "start_color_expression" ) ).toString();
873  if ( properties.contains( QStringLiteral( "end_color_expression" ) ) )
874  symbolLayer->mEndColorExpressionString = properties.value( QStringLiteral( "end_color_expression" ) ).toString();
875 
876  if ( properties.contains( QStringLiteral( "line_width" ) ) )
877  symbolLayer->mLineRender.mStrokeWidth.setFixedStrokeWidth( properties.value( QStringLiteral( "line_width" ) ).toDouble() ) ;
878  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
879  symbolLayer->mLineRender.setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties.value( QStringLiteral( "line_width_unit" ) ).toString() ) );
880  if ( properties.contains( QStringLiteral( "width_varying_minimum_value" ) ) )
881  symbolLayer->mLineRender.mStrokeWidth.setMinimumValue( properties.value( QStringLiteral( "width_varying_minimum_value" ) ).toDouble() );
882  if ( properties.contains( QStringLiteral( "width_varying_maximum_value" ) ) )
883  symbolLayer->mLineRender.mStrokeWidth.setMaximumValue( properties.value( QStringLiteral( "width_varying_maximum_value" ) ).toDouble() );
884  if ( properties.contains( QStringLiteral( "width_varying_use_absolute_value" ) ) )
885  symbolLayer->mLineRender.mStrokeWidth.setUseAbsoluteValue( properties.value( QStringLiteral( "width_varying_use_absolute_value" ) ).toInt() );
886  if ( properties.contains( QStringLiteral( "width_varying_minimum_width" ) ) )
887  symbolLayer->mLineRender.mStrokeWidth.setMinimumWidth( properties.value( QStringLiteral( "width_varying_minimum_width" ) ).toDouble() );
888  if ( properties.contains( QStringLiteral( "width_varying_maximum_width" ) ) )
889  symbolLayer->mLineRender.mStrokeWidth.setMaximumWidth( properties.value( QStringLiteral( "width_varying_maximum_width" ) ).toDouble() );
890  if ( properties.contains( QStringLiteral( "width_varying_ignore_out_of_range" ) ) )
891  symbolLayer->mLineRender.mStrokeWidth.setIgnoreOutOfRange( properties.value( QStringLiteral( "width_varying_ignore_out_of_range" ) ).toInt() );
892  if ( properties.contains( QStringLiteral( "width_varying_is_variable_width" ) ) )
893  symbolLayer->mLineRender.mStrokeWidth.setIsVariableWidth( properties.value( QStringLiteral( "width_varying_is_variable_width" ) ).toInt() );
894 
895  if ( properties.contains( QStringLiteral( "single_color" ) ) )
896  symbolLayer->mLineRender.mStrokeColoring.setColor( QgsSymbolLayerUtils::decodeColor( properties.value( QStringLiteral( "single_color" ) ).toString() ) );
897  if ( properties.contains( QStringLiteral( "color_ramp_shader" ) ) )
898  symbolLayer->mLineRender.mStrokeColoring.setColor( createColorRampShaderFromProperties( properties.value( QStringLiteral( "color_ramp_shader" ) ) ) );
899  if ( properties.contains( QStringLiteral( "coloring_method" ) ) )
900  symbolLayer->mLineRender.mStrokeColoring.setColoringMethod(
901  static_cast<QgsInterpolatedLineColor::ColoringMethod>( properties.value( QStringLiteral( "coloring_method" ) ).toInt() ) );
902 
903  return symbolLayer.release();
904 }
905 
907 {
908  QVariantMap props;
909 
910  props.insert( QStringLiteral( "start_width_expression" ), mStartWidthExpressionString );
911  props.insert( QStringLiteral( "end_width_expression" ), mEndWidthExpressionString );
912  props.insert( QStringLiteral( "start_color_expression" ), mStartColorExpressionString );
913  props.insert( QStringLiteral( "end_color_expression" ), mEndColorExpressionString );
914 
915  // Line width varying
916  props.insert( QStringLiteral( "line_width" ), QString::number( mLineRender.mStrokeWidth.fixedStrokeWidth() ) );
917  props.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineRender.widthUnit() ) );
918  props.insert( QStringLiteral( "width_varying_minimum_value" ), mLineRender.mStrokeWidth.minimumValue() );
919  props.insert( QStringLiteral( "width_varying_maximum_value" ), mLineRender.mStrokeWidth.maximumValue() );
920  props.insert( QStringLiteral( "width_varying_use_absolute_value" ), mLineRender.mStrokeWidth.useAbsoluteValue() ? 1 : 0 );
921  props.insert( QStringLiteral( "width_varying_minimum_width" ), mLineRender.mStrokeWidth.minimumWidth() );
922  props.insert( QStringLiteral( "width_varying_maximum_width" ), mLineRender.mStrokeWidth.maximumWidth() );
923  props.insert( QStringLiteral( "width_varying_ignore_out_of_range" ), mLineRender.mStrokeWidth.ignoreOutOfRange() ? 1 : 0 );
924  props.insert( QStringLiteral( "width_varying_is_variable_width" ), mLineRender.mStrokeWidth.isVariableWidth() ? 1 : 0 );
925 
926  // Color varying
927  props.insert( QStringLiteral( "coloring_method" ), mLineRender.mStrokeColoring.coloringMethod() );
928  props.insert( QStringLiteral( "single_color" ), QgsSymbolLayerUtils::encodeColor( mLineRender.mStrokeColoring.singleColor() ) );
929  props.insert( QStringLiteral( "color_ramp_shader" ), colorRampShaderProperties() );
930 
931  return props;
932 }
933 
935 {
936  QgsGeometry geometry = context.patchShape() ? context.patchShape()->geometry()
938  QgsFeature feature;
939  feature.setGeometry( geometry );
940 
941  startRender( context );
942  mStartWidthAttributeIndex = -1;
943  mEndWidthAttributeIndex = -1;
944  mStartColorAttributeIndex = -1;
945  mEndColorAttributeIndex = -1;
946  double min = std::min( mLineRender.interpolatedLineWidth().minimumValue(), mLineRender.interpolatedColor().colorRampShader().minimumValue() );
947  double max = std::max( mLineRender.interpolatedLineWidth().maximumValue(), mLineRender.interpolatedColor().colorRampShader().maximumValue() );
948 
949  double totalLength = geometry.length();
950  if ( totalLength == 0 )
951  return;
952 
953  double variation = ( max - min ) / totalLength;
954 
955  QPolygonF points = geometry.asQPolygonF();
956  double lengthFromStart = 0;
957  for ( int i = 1; i < points.count(); ++i )
958  {
959  QPointF p1 = points.at( i - 1 );
960  QPointF p2 = points.at( i );
961 
962  double v1 = min + variation * lengthFromStart;
963  QPointF vectDist = p2 - p1;
964  lengthFromStart += sqrt( pow( vectDist.x(), 2 ) + pow( vectDist.y(), 2 ) );
965  double v2 = min + variation * lengthFromStart;
966  mLineRender.renderInDeviceCoordinate( v1, v2, v1, v2, p1, p2, context.renderContext() );
967  }
968 
969  renderPolyline( points, context );
970 }
971 
972 
974 {
975  mStartWidthExpressionString = start;
976  mEndWidthExpressionString = end;
977 }
978 
980 {
981  return mStartWidthExpressionString;
982 }
983 
985 {
986  return mEndWidthExpressionString;
987 }
988 
990 {
991  mLineRender.mStrokeWidthUnit = strokeWidthUnit;
992 }
993 
995 
997 {
998  mLineRender.mStrokeWidth = interpolatedLineWidth;
999 }
1000 
1002 
1004 {
1005  mStartColorExpressionString = start;
1006  mEndColorExpressionString = end;
1007 }
1008 
1010 {
1011  return mStartColorExpressionString;
1012 }
1013 
1015 {
1016  return mEndColorExpressionString;
1017 }
1018 
1020 {
1021  mLineRender.setInterpolatedColor( interpolatedLineColor );
1022 }
1023 
1025 {
1026  return mLineRender.interpolatedColor();
1027 }
1028 
1029 QVariant QgsInterpolatedLineSymbolLayer::colorRampShaderProperties() const
1030 {
1031  const QgsColorRampShader &colorRampShader = mLineRender.mStrokeColoring.colorRampShader();
1032 
1033  QVariantMap props;
1034  if ( colorRampShader.sourceColorRamp() )
1035  props.insert( QStringLiteral( "color_ramp_source" ), QgsSymbolLayerUtils::colorRampToVariant( QString(), colorRampShader.sourceColorRamp() ) );
1036  props.insert( QStringLiteral( "color_ramp_shader_type" ), colorRampShader.colorRampType() );
1037  props.insert( QStringLiteral( "color_ramp_shader_classification_mode" ), colorRampShader.classificationMode() );
1038  QVariantList colorRampItemListVariant;
1039 
1040  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader.colorRampItemList();
1041  for ( const QgsColorRampShader::ColorRampItem &item : colorRampItemList )
1042  {
1043  QVariantMap itemVar;
1044  itemVar[QStringLiteral( "label" )] = item.label;
1045  itemVar[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( item.color );
1046  itemVar[QStringLiteral( "value" )] = item.value;
1047  colorRampItemListVariant.append( itemVar );
1048  }
1049  props.insert( QStringLiteral( "color_ramp_shader_items_list" ), colorRampItemListVariant );
1050 
1051  props.insert( QStringLiteral( "color_ramp_shader_minimum_value" ), colorRampShader.minimumValue() );
1052  props.insert( QStringLiteral( "color_ramp_shader_maximum_value" ), colorRampShader.maximumValue() );
1053  props.insert( QStringLiteral( "color_ramp_shader_value_out_of_range" ), colorRampShader.clip() ? 1 : 0 );
1054  props.insert( QStringLiteral( "color_ramp_shader_label_precision" ), colorRampShader.labelPrecision() );
1055 
1056  return props;
1057 }
1058 
1059 QgsColorRampShader QgsInterpolatedLineSymbolLayer::createColorRampShaderFromProperties( const QVariant &properties )
1060 {
1061  QgsColorRampShader colorRampShader;
1062 
1063  if ( properties.type() != QVariant::Map )
1064  return colorRampShader;
1065 
1066  QVariantMap shaderVariantMap = properties.toMap();
1067 
1068  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_source" ) ) )
1069  colorRampShader.setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( shaderVariantMap.value( QStringLiteral( "color_ramp_source" ) ) ) );
1070 
1071  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_type" ) ) )
1072  colorRampShader.setColorRampType( static_cast<QgsColorRampShader::Type>( shaderVariantMap.value( QStringLiteral( "color_ramp_shader_type" ) ).toInt() ) );
1073  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_classification_mode" ) ) )
1074  colorRampShader.setClassificationMode( static_cast<QgsColorRampShader::ClassificationMode>(
1075  shaderVariantMap.value( QStringLiteral( "color_ramp_shader_classification_mode" ) ).toInt() ) );
1076 
1077  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_items_list" ) ) )
1078  {
1079  QVariant colorRampItemsVar = shaderVariantMap.value( QStringLiteral( "color_ramp_shader_items_list" ) );
1080  if ( colorRampItemsVar.type() == QVariant::List )
1081  {
1082  QVariantList itemVariantList = colorRampItemsVar.toList();
1083  QList<QgsColorRampShader::ColorRampItem> colorRampItemList;
1084  for ( const QVariant &itemVar : std::as_const( itemVariantList ) )
1085  {
1087  if ( itemVar.type() != QVariant::Map )
1088  continue;
1089  QVariantMap itemVarMap = itemVar.toMap();
1090  if ( !itemVarMap.contains( QStringLiteral( "label" ) ) || !itemVarMap.contains( QStringLiteral( "color" ) ) || !itemVarMap.contains( QStringLiteral( "value" ) ) )
1091  continue;
1092 
1093  item.label = itemVarMap.value( QStringLiteral( "label" ) ).toString();
1094  item.color = QgsSymbolLayerUtils::decodeColor( itemVarMap.value( QStringLiteral( "color" ) ).toString() );
1095  item.value = itemVarMap.value( QStringLiteral( "value" ) ).toDouble();
1096 
1097  colorRampItemList.append( item );
1098  }
1099  colorRampShader.setColorRampItemList( colorRampItemList );
1100  }
1101  }
1102 
1103  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_minimum_value" ) ) )
1104  colorRampShader.setMinimumValue( shaderVariantMap.value( QStringLiteral( "color_ramp_shader_minimum_value" ) ).toDouble() );
1105  else
1106  colorRampShader.setMinimumValue( std::numeric_limits<double>::quiet_NaN() );
1107 
1108  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_maximum_value" ) ) )
1109  colorRampShader.setMaximumValue( shaderVariantMap.value( QStringLiteral( "color_ramp_shader_maximum_value" ) ).toDouble() );
1110  else
1111  colorRampShader.setMaximumValue( std::numeric_limits<double>::quiet_NaN() );
1112 
1113  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_value_out_of_range" ) ) )
1114  colorRampShader.setClip( shaderVariantMap.value( QStringLiteral( "color_ramp_shader_value_out_of_range" ) ).toInt() == 1 );
1115  if ( shaderVariantMap.contains( QStringLiteral( "color_ramp_shader_label_precision" ) ) )
1116  colorRampShader.setLabelPrecision( shaderVariantMap.value( QStringLiteral( "color_ramp_shader_label_precision" ) ).toInt() );
1117 
1118  return colorRampShader;
1119 }
1120 
1122 
1123 
1125 {
1126  mFeature = feature;
1127 }
1128 
1130 {
1131  mFeature = QgsFeature();
1132 }
1133 
1135 {
1136  Q_UNUSED( points ); //this symbol layer need to used all the feature geometry, not clipped/simplified geometry
1137 
1138  QVector<QgsPolylineXY> lineStrings;
1139 
1140  double startValWidth = 0;
1141  double endValWidth = 0;
1142  double variationPerMapUnitWidth = 0;
1143  double startValColor = 0;
1144  double endValColor = 0;
1145  double variationPerMapUnitColor = 0;
1146 
1147  QgsRenderContext renderContext = context.renderContext();
1148 
1149  QgsGeometry geom = mFeature.geometry();
1150 
1151  mLineRender.setSelected( context.selected() );
1152 
1153  if ( geom.isEmpty() )
1154  return;
1155 
1156  switch ( QgsWkbTypes::flatType( geom.wkbType() ) )
1157  {
1158  case QgsWkbTypes::Unknown:
1159  case QgsWkbTypes::Point:
1160  case QgsWkbTypes::Polygon:
1161  case QgsWkbTypes::Triangle:
1168  return;
1169  break;
1173  lineStrings.append( geom.asPolyline() );
1174  break;
1177  lineStrings = geom.asMultiPolyline();
1178  break;
1179  default:
1180  return;
1181  break;
1182  }
1183 
1184  QgsExpressionContext expressionContext = renderContext.expressionContext();
1185  expressionContext.setFeature( mFeature );
1186 
1187  double totalLength = geom.length();
1188 
1189  if ( totalLength == 0 )
1190  return;
1191 
1192  QVariant val1WidthVariant;
1193  QVariant val2WidthVariant;
1194  QVariant val1ColorVariant;
1195  QVariant val2ColorVariant;
1196  bool ok = true;
1197 
1198  if ( mLineRender.interpolatedLineWidth().isVariableWidth() )
1199  {
1200  if ( mStartWidthExpression )
1201  {
1202  val1WidthVariant = mStartWidthExpression->evaluate( &expressionContext );
1203  ok |= mStartWidthExpression->hasEvalError();
1204  }
1205  else
1206  val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
1207 
1208  if ( mEndWithExpression )
1209  {
1210  val2WidthVariant = mEndWithExpression->evaluate( &expressionContext );
1211  ok |= mEndWithExpression->hasEvalError();
1212  }
1213  else
1214  val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
1215 
1216  if ( !ok )
1217  return;
1218 
1219  startValWidth = val1WidthVariant.toDouble( &ok );
1220  if ( !ok )
1221  return;
1222 
1223  endValWidth = val2WidthVariant.toDouble( &ok );
1224  if ( !ok )
1225  return;
1226 
1227  variationPerMapUnitWidth = ( endValWidth - startValWidth ) / totalLength;
1228  }
1229 
1231  {
1232  if ( mStartColorExpression )
1233  {
1234  val1ColorVariant = mStartColorExpression->evaluate( &expressionContext );
1235  ok |= mStartColorExpression->hasEvalError();
1236  }
1237  else
1238  val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
1239 
1240  if ( mEndColorExpression )
1241  {
1242  val2ColorVariant = mEndColorExpression->evaluate( &expressionContext );
1243  ok |= mEndColorExpression->hasEvalError();
1244  }
1245  else
1246  val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
1247 
1248  startValColor = val1ColorVariant.toDouble( &ok );
1249  if ( !ok )
1250  return;
1251 
1252  endValColor = val2ColorVariant.toDouble( &ok );
1253  if ( !ok )
1254  return;
1255 
1256  variationPerMapUnitColor = ( endValColor - startValColor ) / totalLength;
1257  }
1258 
1259  for ( const QgsPolylineXY &poly : std::as_const( lineStrings ) )
1260  {
1261  double lengthFromStart = 0;
1262  for ( int i = 1; i < poly.count(); ++i )
1263  {
1264  QgsPointXY p1 = poly.at( i - 1 );
1265  QgsPointXY p2 = poly.at( i );
1266 
1267  double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
1268  double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
1269  lengthFromStart += p1.distance( p2 );
1270  double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
1271  double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
1272  mLineRender.render( v1c, v2c, v1w, v2w, p1, p2, renderContext );
1273  }
1274  }
1275 
1276 }
1277 
1279 {
1280  return symbol && symbol->type() == Qgis::SymbolType::Line;
1281 }
1282 
1284 {
1285  QSet<QString> attributes;
1286 
1287  // mFirstValueExpression and mSecondValueExpression can contain either attribute name or an expression.
1288  // Sometimes it is not possible to distinguish between those two,
1289  // e.g. "a - b" can be both a valid attribute name or expression.
1290  // Since we do not have access to fields here, try both options.
1291  attributes << mStartWidthExpressionString;
1292  attributes << mEndWidthExpressionString;
1293  attributes << mStartColorExpressionString;
1294  attributes << mEndColorExpressionString;
1295 
1296  QgsExpression testExprStartWidth( mStartWidthExpressionString );
1297  if ( !testExprStartWidth.hasParserError() )
1298  attributes.unite( testExprStartWidth.referencedColumns() );
1299 
1300  QgsExpression testExprEndWidth( mEndWidthExpressionString );
1301  if ( !testExprEndWidth.hasParserError() )
1302  attributes.unite( testExprEndWidth.referencedColumns() );
1303 
1304  QgsExpression testExprStartColor( mEndWidthExpressionString );
1305  if ( !testExprStartColor.hasParserError() )
1306  attributes.unite( testExprStartColor.referencedColumns() );
1307 
1308  QgsExpression testExprEndColor( mEndWidthExpressionString );
1309  if ( !testExprEndColor.hasParserError() )
1310  attributes.unite( testExprEndColor.referencedColumns() );
1311 
1312  return attributes;
1313 }
1314 
1316 {
1317  return true;
1318 }
@ Line
Line symbol.
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Returns the custom colormap.
ClassificationMode classificationMode() const
Returns the classification mode.
bool isEmpty() const
Whether the color ramp contains any items.
Type colorRampType() const
Returns the color ramp type.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
ClassificationMode
Classification modes used to create the color ramp shader.
void setClip(bool clip)
Sets whether the shader should not render values out of range.
bool clip() const
Returns whether the shader will clip values which are out of range.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context=QgsReadWriteContext()) const
Writes configuration to a new DOM element.
bool shade(double value, int *returnRedValue, int *returnGreenValue, int *returnBlueValue, int *returnAlphaValue) const override
Generates and new RGB value based on one input value.
QgsColorRamp * sourceColorRamp() const
Returns the source color ramp.
Type
Supported methods for color interpolation.
@ Interpolated
Interpolates the color between two class breaks linearly.
@ Discrete
Assigns the color of the higher class for every pixel between two class breaks.
@ Exact
Assigns the color of the exact matching value in the color ramp item list.
void setClassificationMode(ClassificationMode classificationMode)
Sets classification mode.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Sets a custom colormap.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Sets the color ramp type.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context=QgsReadWriteContext())
Reads configuration from the given DOM element.
Abstract base class for color ramps.
Definition: qgscolorramp.h:32
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:302
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:145
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
double length() const
Returns the planar, 2-dimensional length of geometry.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
QPolygonF asQPolygonF() const SIP_HOLDGIL
Returns contents of the geometry as a QPolygonF.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Class defining color to render mesh datasets.
QgsInterpolatedLineColor::ColoringMethod coloringMethod() const
Returns the coloring method used.
QgsColorRampShader colorRampShader() const
Returns the color ramp shader.
void setColoringMethod(const QgsInterpolatedLineColor::ColoringMethod &coloringMethod)
Sets the coloring method used.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Reads configuration from the given DOM element.
QColor color(double magnitude) const
Returns the color corresponding to the magnitude.
void graduatedColors(double value1, double value2, QList< double > &breakValues, QList< QColor > &breakColors, QList< QLinearGradient > &gradients) const
Returns the break values, graduated colors and the associated gradients between two values.
QgsInterpolatedLineColor()
Default constructor.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Writes configuration to a new DOM element.
void setColor(const QgsColorRampShader &colorRampShader)
Sets the color ramp to define the coloring.
QColor singleColor() const
Returns the single color that is used if SingleColor coloring mode is set.
ColoringMethod
Defines how the color is defined.
@ ColorRamp
Render with a color ramp.
@ SingleColor
Render with a single color.
void setWidthUnit(const QgsUnitTypes::RenderUnit &strokeWidthUnit)
Sets the unit of the stroke width.
void setInterpolatedColor(const QgsInterpolatedLineColor &strokeColoring)
Sets the stroke color used to render.
QgsUnitTypes::RenderUnit widthUnit() const
Returns the unit of the stroke width.
void setInterpolatedWidth(const QgsInterpolatedLineWidth &strokeWidth)
Sets the stroke width used to render.
void setSelected(bool selected)
Sets if the rendering must be done as the element is selected.
void render(double value1, double value2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context) const
Renders a line in the context between point1 and point2 with color and width that vary depending on v...
QgsInterpolatedLineWidth interpolatedLineWidth() const
Returns the stroke width used to render.
QgsInterpolatedLineColor interpolatedColor() const
Returns the stroke color used to render.
A symbol layer that represents vector layer line feature as interpolated line The interpolation is do...
QString endValueExpressionForWidth() const
Returns the expression related to the end extremity value for width.
QString endValueExpressionForColor() const
Returns the expression related to the end extremity value for width for color.
QgsInterpolatedLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
QgsInterpolatedLineColor interpolatedColor() const
Returns the interpolated color used to render the colors of lines, see QgsInterpolatedLineColor.
void setWidthUnit(const QgsUnitTypes::RenderUnit &strokeWidthUnit)
Sets the width unit.
bool isCompatibleWithSymbol(QgsSymbol *symbol) const override
Returns if the layer can be used below the specified symbol.
QString startValueExpressionForColor() const
Returns the epression related to the start extremity value for width for color.
void setExpressionsStringForWidth(QString start, QString end)
Sets the expressions (as string) that define the extremety values af the line feature for width.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
QString layerType() const override
Returns a string that represents this layer type.
void setInterpolatedWidth(const QgsInterpolatedLineWidth &interpolatedLineWidth)
Sets the interpolated width used to render the width of lines, see QgsInterpolatedLineWidth.
void setInterpolatedColor(const QgsInterpolatedLineColor &interpolatedLineColor)
Sets the interpolated color used to render the colors of lines, see QgsInterpolatedLineColor.
QString startValueExpressionForWidth() const
Returns the epression related to the start extremity value for width.
void setExpressionsStringForColor(QString start, QString end)
Sets the expressions (as string) that define the extremety values af the line feature for color.
void drawPreviewIcon(QgsSymbolRenderContext &context, QSize size) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsUnitTypes::RenderUnit widthUnit() const
Returns the width unit.
QgsInterpolatedLineWidth interpolatedWidth() const
Returns the interpolated width used to render the width of lines, see QgsInterpolatedLineWidth.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
static QgsSymbolLayer * create(const QVariantMap &properties)
Creates the symbol layer.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
Represents a width than can vary depending on values.
void setFixedStrokeWidth(double fixedWidth)
Sets the fixed width.
double strokeWidth(double value) const
Returns the variable width depending on value, if not varying returns the fixed width.
void setUseAbsoluteValue(bool useAbsoluteValue)
Sets whether absolute value are used as input.
double minimumValue() const
Returns the minimum value used to defined the variable width.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Reads configuration from the given DOM element.
void setIgnoreOutOfRange(bool ignoreOutOfRange)
Sets whether the variable width ignores out of range value.
void setMaximumValue(double maximumValue)
Sets the maximum value used to defined the variable width.
bool useAbsoluteValue() const
Returns whether absolute value are used as input.
void setIsVariableWidth(bool isVariableWidth)
Returns whether the width is variable.
void setMinimumValue(double minimumValue)
Sets the minimum value used to defined the variable width.
double maximumWidth() const
Returns the maximum width used to defined the variable width.
void setMaximumWidth(double maximumWidth)
Sets the maximum width used to defined the variable width.
double maximumValue() const
Returns the maximum value used to defined the variable width.
void setMinimumWidth(double minimumWidth)
Sets the minimum width used to defined the variable width.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Writes configuration to a new DOM element.
bool ignoreOutOfRange() const
Returns whether the variable width ignores out of range value.
double minimumWidth() const
Returns the minimum width used to defined the variable width.
double fixedStrokeWidth() const
Returns the fixed width.
bool isVariableWidth() const
Returns whether the width is variable.
QgsGeometry geometry() const
Returns the geometry for the patch shape.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
QgsPointXY transform(const QgsPointXY &p) const
Transform the point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:82
A class to represent a 2D point.
Definition: qgspointxy.h:59
double distance(double x, double y) const SIP_HOLDGIL
Returns the distance between this point and a specified x, y coordinate.
Definition: qgspointxy.h:211
QPointF toQPointF() const
Converts a point to a QPointF.
Definition: qgspointxy.h:169
double maximumValue() const
Returns the minimum value for the raster shader.
void setLabelPrecision(int labelPrecision)
Sets label precision to labelPrecision.
int labelPrecision() const
Returns label precision.
virtual void setMaximumValue(double value)
Sets the maximum value for the raster shader.
virtual void setMinimumValue(double value)
Sets the minimum value for the raster shader.
double minimumValue() const
Returns the maximum value for the raster shader.
The class is used as a container of context for various read/write operations on other objects.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QColor selectionColor() const
Returns the color to use when rendering selected features.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
Scoped object for saving and restoring a QPainter object's state.
QgsLegendPatchShape defaultPatch(Qgis::SymbolType type, QSizeF size) const
Returns the default legend patch shape for the given symbol type.
Definition: qgsstyle.cpp:1173
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:131
static QVariant colorRampToVariant(const QString &name, QgsColorRamp *ramp)
Saves a color ramp to a QVariantMap, wrapped in a QVariant.
static QColor decodeColor(const QString &str)
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static QString encodeColor(const QColor &color)
QgsFields mFields
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
const QgsLegendPatchShape * patchShape() const
Returns the symbol patch shape, to use if rendering symbol preview icons.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:38
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:97
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
@ GeometryCollection
Definition: qgswkbtypes.h:79
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:702
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:51