QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsarrowsymbollayer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsarrowsymbollayer.cpp
3  ---------------------
4  begin : January 2016
5  copyright : (C) 2016 by Hugo Mercier
6  email : hugo dot mercier at oslandia 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 "qgsarrowsymbollayer.h"
17 #include "qgssymbollayerutils.h"
18 
20 {
21  /* default values */
22  setOffset( 0.0 );
24 
25  mSymbol.reset( static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
26 }
27 
29 {
30  if ( symbol && symbol->type() == QgsSymbol::Fill )
31  {
32  mSymbol.reset( static_cast<QgsFillSymbol *>( symbol ) );
33  return true;
34  }
35  delete symbol;
36  return false;
37 }
38 
39 QgsSymbolLayer *QgsArrowSymbolLayer::create( const QVariantMap &props )
40 {
42 
43  if ( props.contains( QStringLiteral( "arrow_width" ) ) )
44  l->setArrowWidth( props[QStringLiteral( "arrow_width" )].toDouble() );
45 
46  if ( props.contains( QStringLiteral( "arrow_width_unit" ) ) )
47  l->setArrowWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "arrow_width_unit" )].toString() ) );
48 
49  if ( props.contains( QStringLiteral( "arrow_width_unit_scale" ) ) )
50  l->setArrowWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_width_unit_scale" )].toString() ) );
51 
52  if ( props.contains( QStringLiteral( "arrow_start_width" ) ) )
53  l->setArrowStartWidth( props[QStringLiteral( "arrow_start_width" )].toDouble() );
54 
55  if ( props.contains( QStringLiteral( "arrow_start_width_unit" ) ) )
56  l->setArrowStartWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "arrow_start_width_unit" )].toString() ) );
57 
58  if ( props.contains( QStringLiteral( "arrow_start_width_unit_scale" ) ) )
59  l->setArrowStartWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_start_width_unit_scale" )].toString() ) );
60 
61  if ( props.contains( QStringLiteral( "is_curved" ) ) )
62  l->setIsCurved( props[QStringLiteral( "is_curved" )].toInt() == 1 );
63 
64  if ( props.contains( QStringLiteral( "is_repeated" ) ) )
65  l->setIsRepeated( props[QStringLiteral( "is_repeated" )].toInt() == 1 );
66 
67  if ( props.contains( QStringLiteral( "head_length" ) ) )
68  l->setHeadLength( props[QStringLiteral( "head_length" )].toDouble() );
69 
70  if ( props.contains( QStringLiteral( "head_length_unit" ) ) )
71  l->setHeadLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "head_length_unit" )].toString() ) );
72 
73  if ( props.contains( QStringLiteral( "head_length_unit_scale" ) ) )
74  l->setHeadLengthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_length_unit_scale" )].toString() ) );
75 
76  if ( props.contains( QStringLiteral( "head_thickness" ) ) )
77  l->setHeadThickness( props[QStringLiteral( "head_thickness" )].toDouble() );
78 
79  if ( props.contains( QStringLiteral( "head_thickness_unit" ) ) )
80  l->setHeadThicknessUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "head_thickness_unit" )].toString() ) );
81 
82  if ( props.contains( QStringLiteral( "head_thickness_unit_scale" ) ) )
83  l->setHeadThicknessUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_thickness_unit_scale" )].toString() ) );
84 
85  if ( props.contains( QStringLiteral( "head_type" ) ) )
86  l->setHeadType( static_cast<HeadType>( props[QStringLiteral( "head_type" )].toInt() ) );
87 
88  if ( props.contains( QStringLiteral( "arrow_type" ) ) )
89  l->setArrowType( static_cast<ArrowType>( props[QStringLiteral( "arrow_type" )].toInt() ) );
90 
91  if ( props.contains( QStringLiteral( "offset" ) ) )
92  l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
93 
94  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
95  l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
96 
97  if ( props.contains( QStringLiteral( "offset_unit_scale" ) ) )
98  l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_unit_scale" )].toString() ) );
99 
100  if ( props.contains( QStringLiteral( "ring_filter" ) ) )
101  l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
102 
104 
106 
107  return l;
108 }
109 
111 {
112  QgsArrowSymbolLayer *l = static_cast<QgsArrowSymbolLayer *>( create( properties() ) );
113  l->setSubSymbol( mSymbol->clone() );
115  copyPaintEffect( l );
116  return l;
117 }
118 
120 {
121  return QStringLiteral( "ArrowLine" );
122 }
123 
125 {
126  QVariantMap map;
127 
128  map[QStringLiteral( "arrow_width" )] = QString::number( arrowWidth() );
129  map[QStringLiteral( "arrow_width_unit" )] = QgsUnitTypes::encodeUnit( arrowWidthUnit() );
130  map[QStringLiteral( "arrow_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowWidthUnitScale() );
131 
132  map[QStringLiteral( "arrow_start_width" )] = QString::number( arrowStartWidth() );
133  map[QStringLiteral( "arrow_start_width_unit" )] = QgsUnitTypes::encodeUnit( arrowStartWidthUnit() );
134  map[QStringLiteral( "arrow_start_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowStartWidthUnitScale() );
135 
136  map[QStringLiteral( "is_curved" )] = QString::number( isCurved() ? 1 : 0 );
137  map[QStringLiteral( "is_repeated" )] = QString::number( isRepeated() ? 1 : 0 );
138 
139  map[QStringLiteral( "head_length" )] = QString::number( headLength() );
140  map[QStringLiteral( "head_length_unit" )] = QgsUnitTypes::encodeUnit( headLengthUnit() );
141  map[QStringLiteral( "head_length_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headLengthUnitScale() );
142 
143  map[QStringLiteral( "head_thickness" )] = QString::number( headThickness() );
144  map[QStringLiteral( "head_thickness_unit" )] = QgsUnitTypes::encodeUnit( headThicknessUnit() );
145  map[QStringLiteral( "head_thickness_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headThicknessUnitScale() );
146 
147  map[QStringLiteral( "head_type" )] = QString::number( headType() );
148  map[QStringLiteral( "arrow_type" )] = QString::number( arrowType() );
149 
150  map[QStringLiteral( "offset" )] = QString::number( offset() );
151  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( offsetUnit() );
152  map[QStringLiteral( "offset_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetMapUnitScale() );
153 
154  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
155 
156  return map;
157 }
158 
159 QSet<QString> QgsArrowSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
160 {
161  QSet<QString> attributes = QgsLineSymbolLayer::usedAttributes( context );
162 
163  attributes.unite( mSymbol->usedAttributes( context ) );
164 
165  return attributes;
166 }
167 
169 {
171  return true;
172  if ( mSymbol && mSymbol->hasDataDefinedProperties() )
173  return true;
174  return false;
175 }
176 
178 {
179  return mArrowWidthUnit == QgsUnitTypes::RenderMapUnits || mArrowWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
180  || mArrowStartWidthUnit == QgsUnitTypes::RenderMapUnits || mArrowStartWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
181  || mHeadLengthUnit == QgsUnitTypes::RenderMapUnits || mHeadLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
182  || mHeadThicknessUnit == QgsUnitTypes::RenderMapUnits || mHeadThicknessUnit == QgsUnitTypes::RenderMetersInMapUnits
185 }
186 
188 {
189  mExpressionScope.reset( new QgsExpressionContextScope() );
190  mScaledArrowWidth = context.renderContext().convertToPainterUnits( arrowWidth(), arrowWidthUnit(), arrowWidthUnitScale() );
192  mScaledHeadLength = context.renderContext().convertToPainterUnits( headLength(), headLengthUnit(), headLengthUnitScale() );
194  mScaledOffset = context.renderContext().convertToPainterUnits( offset(), offsetUnit(), offsetMapUnitScale() );
195  mComputedHeadType = headType();
196  mComputedArrowType = arrowType();
197 
198  mSymbol->startRender( context.renderContext() );
199 }
200 
202 {
203  mSymbol->stopRender( context.renderContext() );
204 }
205 
206 inline qreal euclidean_distance( QPointF po, QPointF pd )
207 {
208  return std::sqrt( ( po.x() - pd.x() ) * ( po.x() - pd.x() ) + ( po.y() - pd.y() ) * ( po.y() - pd.y() ) );
209 }
210 
211 QPolygonF straightArrow( QPointF po, QPointF pd,
212  qreal startWidth, qreal width,
213  qreal headWidth, qreal headHeight,
215  qreal offset )
216 {
217  QPolygonF polygon; // implicitly shared
218  // vector length
219  qreal length = euclidean_distance( po, pd );
220 
221  // shift points if there is not enough room for the head(s)
222  if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
223  {
224  po = pd - ( pd - po ) / length * headWidth;
225  length = headWidth;
226  }
227  else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
228  {
229  pd = po + ( pd - po ) / length * headWidth;
230  length = headWidth;
231  }
232  else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
233  {
234  QPointF v = ( pd - po ) / length * headWidth;
235  QPointF npo = ( po + pd ) / 2.0 - v;
236  QPointF npd = ( po + pd ) / 2.0 + v;
237  po = npo;
238  pd = npd;
239  length = 2 * headWidth;
240  }
241 
242  qreal bodyLength = length - headWidth;
243 
244  // unit vector
245  QPointF unitVec = ( pd - po ) / length;
246  // perpendicular vector
247  QPointF perpVec( -unitVec.y(), unitVec.x() );
248 
249  // set offset
250  po += perpVec * offset;
251  pd += perpVec * offset;
252 
253  if ( headType == QgsArrowSymbolLayer::HeadDouble )
254  {
255  // first head
256  polygon << po;
257  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
258  {
259  polygon << po + unitVec *headWidth + perpVec *headHeight;
260  polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
261 
262  polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
263 
264  // second head
265  polygon << po + unitVec *bodyLength + perpVec *headHeight;
266  }
267  polygon << pd;
268 
269  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
270  {
271  polygon << po + unitVec *bodyLength - perpVec *headHeight;
272  polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
273 
274  // end of the first head
275  polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
276  polygon << po + unitVec *headWidth - perpVec *headHeight;
277  }
278  }
279  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
280  {
281  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
282  {
283  polygon << po + perpVec * ( startWidth * 0.5 );
284  polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
285  polygon << po + unitVec *bodyLength + perpVec *headHeight;
286  }
287  else
288  {
289  polygon << po;
290  }
291  polygon << pd;
292  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
293  {
294  polygon << po + unitVec *bodyLength - perpVec *headHeight;
295  polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
296  polygon << po - perpVec * ( startWidth * 0.5 );
297  }
298  else
299  {
300  polygon << po;
301  }
302  }
303  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
304  {
305  polygon << po;
306  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
307  {
308  polygon << po + unitVec *headWidth + perpVec *headHeight;
309  polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
310 
311  polygon << pd + perpVec * ( startWidth * 0.5 );
312  }
313  else
314  {
315  polygon << pd;
316  }
317  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
318  {
319  polygon << pd - perpVec * ( startWidth * 0.5 );
320 
321  polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
322  polygon << po + unitVec *headWidth - perpVec *headHeight;
323  }
324  else
325  {
326  polygon << pd;
327  }
328  }
329  // close the polygon
330  polygon << polygon.first();
331 
332  return polygon;
333 }
334 
335 // Make sure a given angle is between 0 and 2 pi
336 inline qreal clampAngle( qreal a )
337 {
338  if ( a > 2 * M_PI )
339  return a - 2 * M_PI;
340  if ( a < 0.0 )
341  return a + 2 * M_PI;
342  return a;
343 }
344 
349 bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius )
350 {
351  qreal cx, cy;
352 
353  // AB and BC vectors
354  QPointF ab = b - a;
355  QPointF bc = c - b;
356 
357  // AB and BC middles
358  QPointF ab2 = ( a + b ) / 2.0;
359  QPointF bc2 = ( b + c ) / 2.0;
360 
361  // Aligned points
362  if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
363  return false;
364 
365  // in case AB is horizontal
366  if ( ab.y() == 0 )
367  {
368  cx = ab2.x();
369  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
370  }
371  //# BC horizontal
372  else if ( bc.y() == 0 )
373  {
374  cx = bc2.x();
375  cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
376  }
377  // Otherwise
378  else
379  {
380  cx = ( bc2.y() - ab2.y() + bc.x() * bc2.x() / bc.y() - ab.x() * ab2.x() / ab.y() ) / ( bc.x() / bc.y() - ab.x() / ab.y() );
381  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
382  }
383  // Radius
384  radius = std::sqrt( ( a.x() - cx ) * ( a.x() - cx ) + ( a.y() - cy ) * ( a.y() - cy ) );
385  // Center
386  center.setX( cx );
387  center.setY( cy );
388  return true;
389 }
390 
391 QPointF circlePoint( QPointF center, qreal radius, qreal angle )
392 {
393  // Y is oriented downward
394  return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
395 }
396 
397 void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
398 {
399  QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
400  if ( direction == 1 )
401  {
402  if ( angle_o < angle_d )
403  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
404  else
405  path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
406  }
407  else
408  {
409  if ( angle_o < angle_d )
410  path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
411  else
412  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
413  }
414 }
415 
416 // Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
417 void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
418 {
419  // start point
420  QPointF A = circlePoint( center, startRadius, startAngle );
421  // end point
422  QPointF B = circlePoint( center, endRadius, endAngle );
423  // middle points
424  qreal deltaAngle;
425 
426  deltaAngle = endAngle - startAngle;
427  if ( direction * deltaAngle < 0.0 )
428  deltaAngle = deltaAngle + direction * 2 * M_PI;
429 
430  QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
431  QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
432  QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
433 
434  qreal cRadius;
435  QPointF cCenter;
436  // first circle arc
437  if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
438  {
439  // aligned points => draw a straight line
440  path.lineTo( I2 );
441  }
442  else
443  {
444  // angles in the new circle
445  qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
446  qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
447  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
448  }
449 
450  // second circle arc
451  if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
452  {
453  // aligned points => draw a straight line
454  path.lineTo( B );
455  }
456  else
457  {
458  // angles in the new circle
459  qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
460  qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
461  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
462  }
463 }
464 
465 QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
466  qreal startWidth, qreal width,
467  qreal headWidth, qreal headHeight,
469  qreal offset )
470 {
471  qreal circleRadius;
472  QPointF circleCenter;
473  if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
474  {
475  // aligned points => draw a straight arrow
476  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
477  }
478 
479  // angles of each point
480  qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
481  qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
482  qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
483 
484  // arc direction : 1 = counter-clockwise, -1 = clockwise
485  int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
486 
487  // arrow type, independent of the direction
488  int aType = 0;
489  if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
490  aType = direction;
491  else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
492  aType = -direction;
493 
494  qreal deltaAngle = angle_d - angle_o;
495  if ( direction * deltaAngle < 0.0 )
496  deltaAngle = deltaAngle + direction * 2 * M_PI;
497 
498  qreal length = euclidean_distance( po, pd );
499  // for close points and deltaAngle < 180, draw a straight line
500  if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
501  ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
502  ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
503  {
504  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
505  }
506 
507  // adjust coordinates to include offset
508  circleRadius += offset;
509  po = circlePoint( circleCenter, circleRadius, angle_o );
510  pm = circlePoint( circleCenter, circleRadius, angle_m );
511  pd = circlePoint( circleCenter, circleRadius, angle_d );
512 
513  qreal headAngle = direction * std::atan( headWidth / circleRadius );
514 
515  QPainterPath path;
516 
517  if ( headType == QgsArrowSymbolLayer::HeadDouble )
518  {
519  // the first head
520  path.moveTo( po );
521  if ( aType <= 0 )
522  {
523  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
524 
525  pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
526 
527  // the second head
528  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
529  path.lineTo( pd );
530  }
531  else
532  {
533  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
534  }
535  if ( aType >= 0 )
536  {
537  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
538 
539  pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
540 
541  // the end of the first head
542  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
543  path.lineTo( po );
544  }
545  else
546  {
547  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
548  }
549  }
550  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
551  {
552  if ( aType <= 0 )
553  {
554  path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
555 
556  spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
557 
558  // the arrow head
559  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
560  path.lineTo( pd );
561  }
562  else
563  {
564  path.moveTo( po );
565  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
566  }
567  if ( aType >= 0 )
568  {
569  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
570 
571  spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
572 
573  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
574  }
575  else
576  {
577  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
578  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
579  }
580  }
581  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
582  {
583  path.moveTo( po );
584  if ( aType <= 0 )
585  {
586  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
587  path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
588 
589  spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
590  }
591  else
592  {
593  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
594  }
595  if ( aType >= 0 )
596  {
597  path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
598 
599  spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
600 
601  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
602  path.lineTo( po );
603  }
604  else
605  {
606  path.lineTo( pd );
607  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
608  }
609  }
610 
611  return path.toSubpathPolygons().at( 0 );
612 }
613 
614 void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
615 {
616  if ( !dataDefinedProperties().hasActiveProperties() )
617  return; // shortcut if case there is no data defined properties at all
618 
619  QVariant exprVal;
620  bool ok;
622  {
624  double w = exprVal.toDouble( &ok );
625  if ( ok )
626  {
627  mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
628  }
629  }
631  {
634  double w = exprVal.toDouble( &ok );
635  if ( ok )
636  {
637  mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
638  }
639  }
641  {
644  double w = exprVal.toDouble( &ok );
645  if ( ok )
646  {
647  mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
648  }
649  }
651  {
654  double w = exprVal.toDouble( &ok );
655  if ( ok )
656  {
657  mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
658  }
659  }
661  {
662  context.setOriginalValueVariable( offset() );
664  double w = exprVal.toDouble( &ok );
665  if ( ok )
666  {
667  mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
668  }
669  }
670 
672  {
673  context.setOriginalValueVariable( headType() );
676  if ( ok )
677  {
678  mComputedHeadType = h;
679  }
680  }
681 
683  {
684  context.setOriginalValueVariable( arrowType() );
687  if ( ok )
688  {
689  mComputedArrowType = h;
690  }
691  }
692 }
693 
694 void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
695 {
696  Q_UNUSED( points )
697 
698  if ( !context.renderContext().painter() )
699  {
700  return;
701  }
702 
703  context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
704  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
706 
707  const double prevOpacity = mSymbol->opacity();
708  mSymbol->setOpacity( prevOpacity * context.opacity() );
709 
710  if ( isCurved() )
711  {
712  _resolveDataDefined( context );
713 
714  if ( ! isRepeated() )
715  {
716  if ( points.size() >= 3 )
717  {
718  // origin point
719  QPointF po( points.at( 0 ) );
720  // middle point
721  QPointF pm( points.at( points.size() / 2 ) );
722  // destination point
723  QPointF pd( points.back() );
724 
725  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
726  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
727  }
728  // straight arrow
729  else if ( points.size() == 2 )
730  {
731  // origin point
732  QPointF po( points.at( 0 ) );
733  // destination point
734  QPointF pd( points.at( 1 ) );
735 
736  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
737  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
738  }
739  }
740  else
741  {
742  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
743  {
744  if ( context.renderContext().renderingStopped() )
745  break;
746 
747  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
748  _resolveDataDefined( context );
749 
750  if ( points.size() - pIdx >= 3 )
751  {
752  // origin point
753  QPointF po( points.at( pIdx ) );
754  // middle point
755  QPointF pm( points.at( pIdx + 1 ) );
756  // destination point
757  QPointF pd( points.at( pIdx + 2 ) );
758 
759  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
760  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
761  }
762  // straight arrow
763  else if ( points.size() - pIdx == 2 )
764  {
765  // origin point
766  QPointF po( points.at( pIdx ) );
767  // destination point
768  QPointF pd( points.at( pIdx + 1 ) );
769 
770  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
771  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
772  }
773  }
774  }
775  }
776  else
777  {
778  if ( !isRepeated() )
779  {
780  _resolveDataDefined( context );
781 
782  if ( !points.isEmpty() )
783  {
784  // origin point
785  QPointF po( points.at( 0 ) );
786  // destination point
787  QPointF pd( points.back() );
788 
789  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
790  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
791  }
792  }
793  else
794  {
795  // only straight arrows
796  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
797  {
798  if ( context.renderContext().renderingStopped() )
799  break;
800 
801  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
802  _resolveDataDefined( context );
803 
804  // origin point
805  QPointF po( points.at( pIdx ) );
806  // destination point
807  QPointF pd( points.at( pIdx + 1 ) );
808 
809  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
810 
811  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
812  }
813  }
814  }
815 
816  mSymbol->setOpacity( prevOpacity );
818 }
819 
820 void QgsArrowSymbolLayer::setColor( const QColor &c )
821 {
822  if ( mSymbol )
823  mSymbol->setColor( c );
824 
825  mColor = c;
826 }
827 
829 {
830  return mSymbol.get() ? mSymbol->color() : mColor;
831 }
832 
834 {
835  return true;
836 }
837 
Line symbol layer used for representing lines as arrows.
bool isCurved() const
Returns whether it is a curved arrow or a straight one.
QgsUnitTypes::RenderUnit headLengthUnit() const
Gets the unit for the head length.
ArrowType arrowType() const
Gets the current arrow type.
QString layerType() const override
Returns a string that represents this layer type.
HeadType
Possible head types.
HeadType headType() const
Gets the current head type.
void setArrowStartWidth(double width)
Sets the arrow start width.
bool isRepeated() const
Returns whether the arrow is repeated along the line or not.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsUnitTypes::RenderUnit arrowWidthUnit() const
Gets the unit for the arrow width.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setArrowWidth(double width)
Sets the arrow width.
QgsUnitTypes::RenderUnit arrowStartWidthUnit() const
Gets the unit for the arrow start width.
void setArrowType(ArrowType type)
Sets the arrow type.
void setHeadLengthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the head length.
QgsMapUnitScale headThicknessUnitScale() const
Gets the scale for the head height.
void setIsCurved(bool isCurved)
Sets whether it is a curved arrow or a straight one.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setHeadThickness(double thickness)
Sets the arrow head height.
QgsArrowSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setIsRepeated(bool isRepeated)
Sets whether the arrow is repeated along the line.
QColor color() const override
The fill color.
double arrowStartWidth() const
Gets current arrow start width. Only meaningful for single headed arrows.
QgsUnitTypes::RenderUnit headThicknessUnit() const
Gets the unit for the head height.
void setArrowStartWidthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the arrow start width.
void setHeadThicknessUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the head height.
void setHeadType(HeadType type)
Sets the head type.
QgsMapUnitScale arrowStartWidthUnitScale() const
Gets the scale for the arrow start width.
double headThickness() const
Gets the current arrow head height.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void setArrowWidthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the arrow width.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Create a new QgsArrowSymbolLayer.
void setColor(const QColor &c) override
The fill color.
ArrowType
Possible arrow types.
void setArrowWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the arrow width.
double arrowWidth() const
Gets current arrow width.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsMapUnitScale arrowWidthUnitScale() const
Gets the scale for the arrow width.
void setArrowStartWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the arrow start width.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setHeadLengthUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the head length.
void setHeadThicknessUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the head height.
QgsMapUnitScale headLengthUnitScale() const
Gets the scale for the head length.
QgsArrowSymbolLayer()
Simple constructor.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setHeadLength(double length)
Sets the arrow head length.
double headLength() const
Gets the current arrow head length.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgssymbol.h:1307
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
Definition: qgssymbol.cpp:1578
RenderRingFilter
Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
QgsUnitTypes::RenderUnit mWidthUnit
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units for the line's offset.
const QgsMapUnitScale & offsetMapUnitScale() const
Returns the map unit scale for the line's offset.
void setOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the line's offset.
void setOffset(double offset)
Sets the line's offset.
RenderRingFilter mRingFilter
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the line's offset.
void setRingFilter(QgsLineSymbolLayer::RenderRingFilter filter)
Sets the line symbol layer's ring filter, which controls which rings are rendered when the line symbo...
double offset() const
Returns the line's offset.
QgsUnitTypes::RenderUnit mOffsetUnit
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
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.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
static QgsArrowSymbolLayer::HeadType decodeArrowHeadType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow head type.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QgsArrowSymbolLayer::ArrowType decodeArrowType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow type.
@ PropertyArrowStartWidth
Arrow tail start width.
@ PropertyArrowHeadLength
Arrow head length.
@ PropertyArrowHeadThickness
Arrow head thickness.
@ PropertyOffset
Symbol offset.
@ PropertyArrowHeadType
Arrow head type.
@ PropertyArrowType
Arrow type.
@ PropertyArrowWidth
Arrow tail width.
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QgsPropertyCollection mDataDefinedProperties
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
Definition: qgssymbol.h:850
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgssymbol.h:794
const QgsFeature * feature() const
Returns the current feature being rendered.
Definition: qgssymbol.h:875
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
Definition: qgssymbol.cpp:1508
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:837
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:65
SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:138
@ Fill
Fill symbol.
Definition: qgssymbol.h:90
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.
@ RenderMetersInMapUnits
Meters value as Map units.
Definition: qgsunittypes.h:175
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:168
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:169
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
void pathArcTo(QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction)
void spiralArcTo(QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction)
qreal euclidean_distance(QPointF po, QPointF pd)
QPointF circlePoint(QPointF center, qreal radius, qreal angle)
QPolygonF straightArrow(QPointF po, QPointF pd, qreal startWidth, qreal width, qreal headWidth, qreal headHeight, QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType, qreal offset)
bool pointsToCircle(QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius)
Compute the circumscribed circle from three points.
qreal clampAngle(qreal a)
QPolygonF curvedArrow(QPointF po, QPointF pm, QPointF pd, qreal startWidth, qreal width, qreal headWidth, qreal headHeight, QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType, qreal offset)
Single variable definition for use within a QgsExpressionContextScope.