QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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( QgsStringMap() ) ) );
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 
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" )] ) );
48 
49  if ( props.contains( QStringLiteral( "arrow_width_unit_scale" ) ) )
50  l->setArrowWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_width_unit_scale" )] ) );
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" )] ) );
57 
58  if ( props.contains( QStringLiteral( "arrow_start_width_unit_scale" ) ) )
59  l->setArrowStartWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_start_width_unit_scale" )] ) );
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" )] ) );
72 
73  if ( props.contains( QStringLiteral( "head_length_unit_scale" ) ) )
74  l->setHeadLengthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_length_unit_scale" )] ) );
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" )] ) );
81 
82  if ( props.contains( QStringLiteral( "head_thickness_unit_scale" ) ) )
83  l->setHeadThicknessUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_thickness_unit_scale" )] ) );
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" )] ) );
96 
97  if ( props.contains( QStringLiteral( "offset_unit_scale" ) ) )
98  l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_unit_scale" )] ) );
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  QgsStringMap 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  mExpressionScope.reset( new QgsExpressionContextScope() );
180  mScaledArrowWidth = context.renderContext().convertToPainterUnits( arrowWidth(), arrowWidthUnit(), arrowWidthUnitScale() );
182  mScaledHeadLength = context.renderContext().convertToPainterUnits( headLength(), headLengthUnit(), headLengthUnitScale() );
184  mScaledOffset = context.renderContext().convertToPainterUnits( offset(), offsetUnit(), offsetMapUnitScale() );
185  mComputedHeadType = headType();
186  mComputedArrowType = arrowType();
187 
188  mSymbol->startRender( context.renderContext() );
189 }
190 
192 {
193  mSymbol->stopRender( context.renderContext() );
194 }
195 
196 inline qreal euclidian_distance( QPointF po, QPointF pd )
197 {
198  return std::sqrt( ( po.x() - pd.x() ) * ( po.x() - pd.x() ) + ( po.y() - pd.y() ) * ( po.y() - pd.y() ) );
199 }
200 
201 QPolygonF straightArrow( QPointF po, QPointF pd,
202  qreal startWidth, qreal width,
203  qreal headWidth, qreal headHeight,
205  qreal offset )
206 {
207  QPolygonF polygon; // implicitly shared
208  // vector length
209  qreal length = euclidian_distance( po, pd );
210 
211  // shift points if there is not enough room for the head(s)
212  if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
213  {
214  po = pd - ( pd - po ) / length * headWidth;
215  length = headWidth;
216  }
217  else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
218  {
219  pd = po + ( pd - po ) / length * headWidth;
220  length = headWidth;
221  }
222  else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
223  {
224  QPointF v = ( pd - po ) / length * headWidth;
225  QPointF npo = ( po + pd ) / 2.0 - v;
226  QPointF npd = ( po + pd ) / 2.0 + v;
227  po = npo;
228  pd = npd;
229  length = 2 * headWidth;
230  }
231 
232  qreal bodyLength = length - headWidth;
233 
234  // unit vector
235  QPointF unitVec = ( pd - po ) / length;
236  // perpendicular vector
237  QPointF perpVec( -unitVec.y(), unitVec.x() );
238 
239  // set offset
240  po += perpVec * offset;
241  pd += perpVec * offset;
242 
243  if ( headType == QgsArrowSymbolLayer::HeadDouble )
244  {
245  // first head
246  polygon << po;
247  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
248  {
249  polygon << po + unitVec *headWidth + perpVec *headHeight;
250  polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
251 
252  polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
253 
254  // second head
255  polygon << po + unitVec *bodyLength + perpVec *headHeight;
256  }
257  polygon << pd;
258 
259  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
260  {
261  polygon << po + unitVec *bodyLength - perpVec *headHeight;
262  polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
263 
264  // end of the first head
265  polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
266  polygon << po + unitVec *headWidth - perpVec *headHeight;
267  }
268  }
269  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
270  {
271  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
272  {
273  polygon << po + perpVec * ( startWidth * 0.5 );
274  polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
275  polygon << po + unitVec *bodyLength + perpVec *headHeight;
276  }
277  else
278  {
279  polygon << po;
280  }
281  polygon << pd;
282  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
283  {
284  polygon << po + unitVec *bodyLength - perpVec *headHeight;
285  polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
286  polygon << po - perpVec * ( startWidth * 0.5 );
287  }
288  else
289  {
290  polygon << po;
291  }
292  }
293  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
294  {
295  polygon << po;
296  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
297  {
298  polygon << po + unitVec *headWidth + perpVec *headHeight;
299  polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
300 
301  polygon << pd + perpVec * ( startWidth * 0.5 );
302  }
303  else
304  {
305  polygon << pd;
306  }
307  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
308  {
309  polygon << pd - perpVec * ( startWidth * 0.5 );
310 
311  polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
312  polygon << po + unitVec *headWidth - perpVec *headHeight;
313  }
314  else
315  {
316  polygon << pd;
317  }
318  }
319  // close the polygon
320  polygon << polygon.first();
321 
322  return polygon;
323 }
324 
325 // Make sure a given angle is between 0 and 2 pi
326 inline qreal clampAngle( qreal a )
327 {
328  if ( a > 2 * M_PI )
329  return a - 2 * M_PI;
330  if ( a < 0.0 )
331  return a + 2 * M_PI;
332  return a;
333 }
334 
339 bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius )
340 {
341  qreal cx, cy;
342 
343  // AB and BC vectors
344  QPointF ab = b - a;
345  QPointF bc = c - b;
346 
347  // AB and BC middles
348  QPointF ab2 = ( a + b ) / 2.0;
349  QPointF bc2 = ( b + c ) / 2.0;
350 
351  // Aligned points
352  if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
353  return false;
354 
355  // in case AB is horizontal
356  if ( ab.y() == 0 )
357  {
358  cx = ab2.x();
359  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
360  }
361  //# BC horizontal
362  else if ( bc.y() == 0 )
363  {
364  cx = bc2.x();
365  cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
366  }
367  // Otherwise
368  else
369  {
370  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() );
371  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
372  }
373  // Radius
374  radius = std::sqrt( ( a.x() - cx ) * ( a.x() - cx ) + ( a.y() - cy ) * ( a.y() - cy ) );
375  // Center
376  center.setX( cx );
377  center.setY( cy );
378  return true;
379 }
380 
381 QPointF circlePoint( QPointF center, qreal radius, qreal angle )
382 {
383  // Y is oriented downward
384  return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
385 }
386 
387 void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
388 {
389  QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
390  if ( direction == 1 )
391  {
392  if ( angle_o < angle_d )
393  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
394  else
395  path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
396  }
397  else
398  {
399  if ( angle_o < angle_d )
400  path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
401  else
402  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
403  }
404 }
405 
406 // Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
407 void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
408 {
409  // start point
410  QPointF A = circlePoint( center, startRadius, startAngle );
411  // end point
412  QPointF B = circlePoint( center, endRadius, endAngle );
413  // middle points
414  qreal deltaAngle;
415 
416  deltaAngle = endAngle - startAngle;
417  if ( direction * deltaAngle < 0.0 )
418  deltaAngle = deltaAngle + direction * 2 * M_PI;
419 
420  QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
421  QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
422  QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
423 
424  qreal cRadius;
425  QPointF cCenter;
426  // first circle arc
427  if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
428  {
429  // aligned points => draw a straight line
430  path.lineTo( I2 );
431  }
432  else
433  {
434  // angles in the new circle
435  qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
436  qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
437  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
438  }
439 
440  // second circle arc
441  if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
442  {
443  // aligned points => draw a straight line
444  path.lineTo( B );
445  }
446  else
447  {
448  // angles in the new circle
449  qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
450  qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
451  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
452  }
453 }
454 
455 QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
456  qreal startWidth, qreal width,
457  qreal headWidth, qreal headHeight,
459  qreal offset )
460 {
461  qreal circleRadius;
462  QPointF circleCenter;
463  if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
464  {
465  // aligned points => draw a straight arrow
466  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
467  }
468 
469  // angles of each point
470  qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
471  qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
472  qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
473 
474  // arc direction : 1 = counter-clockwise, -1 = clockwise
475  int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
476 
477  // arrow type, independent of the direction
478  int aType = 0;
479  if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
480  aType = direction;
481  else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
482  aType = -direction;
483 
484  qreal deltaAngle = angle_d - angle_o;
485  if ( direction * deltaAngle < 0.0 )
486  deltaAngle = deltaAngle + direction * 2 * M_PI;
487 
488  qreal length = euclidian_distance( po, pd );
489  // for close points and deltaAngle < 180, draw a straight line
490  if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
491  ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
492  ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
493  {
494  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
495  }
496 
497  // ajust coordinates to include offset
498  circleRadius += offset;
499  po = circlePoint( circleCenter, circleRadius, angle_o );
500  pm = circlePoint( circleCenter, circleRadius, angle_m );
501  pd = circlePoint( circleCenter, circleRadius, angle_d );
502 
503  qreal headAngle = direction * std::atan( headWidth / circleRadius );
504 
505  QPainterPath path;
506 
507  if ( headType == QgsArrowSymbolLayer::HeadDouble )
508  {
509  // the first head
510  path.moveTo( po );
511  if ( aType <= 0 )
512  {
513  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
514 
515  pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
516 
517  // the second head
518  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
519  path.lineTo( pd );
520  }
521  else
522  {
523  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
524  }
525  if ( aType >= 0 )
526  {
527  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
528 
529  pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
530 
531  // the end of the first head
532  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
533  path.lineTo( po );
534  }
535  else
536  {
537  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
538  }
539  }
540  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
541  {
542  if ( aType <= 0 )
543  {
544  path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
545 
546  spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
547 
548  // the arrow head
549  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
550  path.lineTo( pd );
551  }
552  else
553  {
554  path.moveTo( po );
555  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
556  }
557  if ( aType >= 0 )
558  {
559  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
560 
561  spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
562 
563  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
564  }
565  else
566  {
567  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
568  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
569  }
570  }
571  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
572  {
573  path.moveTo( po );
574  if ( aType <= 0 )
575  {
576  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
577  path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
578 
579  spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
580  }
581  else
582  {
583  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
584  }
585  if ( aType >= 0 )
586  {
587  path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
588 
589  spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
590 
591  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
592  path.lineTo( po );
593  }
594  else
595  {
596  path.lineTo( pd );
597  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
598  }
599  }
600 
601  return path.toSubpathPolygons().at( 0 );
602 }
603 
604 void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
605 {
606  if ( !dataDefinedProperties().hasActiveProperties() )
607  return; // shortcut if case there is no data defined properties at all
608 
609  QVariant exprVal;
610  bool ok;
612  {
614  double w = exprVal.toDouble( &ok );
615  if ( ok )
616  {
617  mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
618  }
619  }
621  {
624  double w = exprVal.toDouble( &ok );
625  if ( ok )
626  {
627  mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
628  }
629  }
631  {
634  double w = exprVal.toDouble( &ok );
635  if ( ok )
636  {
637  mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
638  }
639  }
641  {
644  double w = exprVal.toDouble( &ok );
645  if ( ok )
646  {
647  mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
648  }
649  }
651  {
652  context.setOriginalValueVariable( offset() );
654  double w = exprVal.toDouble( &ok );
655  if ( ok )
656  {
657  mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
658  }
659  }
660 
662  {
663  context.setOriginalValueVariable( headType() );
666  if ( ok )
667  {
668  mComputedHeadType = h;
669  }
670  }
671 
673  {
674  context.setOriginalValueVariable( arrowType() );
677  if ( ok )
678  {
679  mComputedArrowType = h;
680  }
681  }
682 }
683 
684 void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
685 {
686  Q_UNUSED( points )
687 
688  if ( !context.renderContext().painter() )
689  {
690  return;
691  }
692 
693  context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
694  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
696  if ( isCurved() )
697  {
698  _resolveDataDefined( context );
699 
700  if ( ! isRepeated() )
701  {
702  if ( points.size() >= 3 )
703  {
704  // origin point
705  QPointF po( points.at( 0 ) );
706  // middle point
707  QPointF pm( points.at( points.size() / 2 ) );
708  // destination point
709  QPointF pd( points.back() );
710 
711  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
712  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
713  }
714  // straight arrow
715  else if ( points.size() == 2 )
716  {
717  // origin point
718  QPointF po( points.at( 0 ) );
719  // destination point
720  QPointF pd( points.at( 1 ) );
721 
722  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
723  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
724  }
725  }
726  else
727  {
728  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
729  {
730  if ( context.renderContext().renderingStopped() )
731  break;
732 
733  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
734  _resolveDataDefined( context );
735 
736  if ( points.size() - pIdx >= 3 )
737  {
738  // origin point
739  QPointF po( points.at( pIdx ) );
740  // middle point
741  QPointF pm( points.at( pIdx + 1 ) );
742  // destination point
743  QPointF pd( points.at( pIdx + 2 ) );
744 
745  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
746  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
747  }
748  // straight arrow
749  else if ( points.size() - pIdx == 2 )
750  {
751  // origin point
752  QPointF po( points.at( pIdx ) );
753  // destination point
754  QPointF pd( points.at( pIdx + 1 ) );
755 
756  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
757  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
758  }
759  }
760  }
761  }
762  else
763  {
764  if ( !isRepeated() )
765  {
766  _resolveDataDefined( context );
767 
768  if ( !points.isEmpty() )
769  {
770  // origin point
771  QPointF po( points.at( 0 ) );
772  // destination point
773  QPointF pd( points.back() );
774 
775  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
776  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
777  }
778  }
779  else
780  {
781  // only straight arrows
782  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
783  {
784  if ( context.renderContext().renderingStopped() )
785  break;
786 
787  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
788  _resolveDataDefined( context );
789 
790  // origin point
791  QPointF po( points.at( pIdx ) );
792  // destination point
793  QPointF pd( points.at( pIdx + 1 ) );
794 
795  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
796  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
797  }
798  }
799  }
801 }
802 
803 void QgsArrowSymbolLayer::setColor( const QColor &c )
804 {
805  if ( mSymbol )
806  mSymbol->setColor( c );
807 
808  mColor = c;
809 }
810 
812 {
813  return mSymbol.get() ? mSymbol->color() : mColor;
814 }
815 
void setHeadThicknessUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the head height.
void setHeadLengthUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the head length.
void stopRender(QgsSymbolRenderContext &context) override
Single variable definition for use within a QgsExpressionContextScope.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
QgsUnitTypes::RenderUnit headThicknessUnit() const
Gets the unit for the head height.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
static QgsArrowSymbolLayer::ArrowType decodeArrowType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow type.
double arrowStartWidth() const
Gets current arrow start width. Only meaningful for single headed arrows.
double arrowWidth() const
Gets current arrow width.
void setArrowWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the arrow width.
const QgsMapUnitScale & offsetMapUnitScale() const
Returns the map unit scale for the line&#39;s offset.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
static QgsFillSymbol * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
Definition: qgssymbol.cpp:1251
QPolygonF straightArrow(QPointF po, QPointF pd, qreal startWidth, qreal width, qreal headWidth, qreal headHeight, QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType, qreal offset)
QgsMapUnitScale arrowStartWidthUnitScale() const
Gets the scale for the arrow start width.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsMapUnitScale headLengthUnitScale() const
Gets the scale for the head length.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
void pathArcTo(QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
void setHeadThicknessUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the head height.
void restoreOldDataDefinedProperties(const QgsStringMap &stringMap)
Restores older data defined properties from string map.
void setColor(const QColor &c) override
The fill color.
qreal clampAngle(qreal a)
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units for the line&#39;s offset.
QPolygonF curvedArrow(QPointF po, QPointF pm, QPointF pd, qreal startWidth, qreal width, qreal headWidth, qreal headHeight, QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType, qreal offset)
ArrowType arrowType() const
Gets the current arrow type.
static QgsArrowSymbolLayer::HeadType decodeArrowHeadType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow head type.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer&#39;s subsymbol. takes ownership of the passed symbol.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:612
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
virtual double width() const
Returns the estimated width for the line symbol layer.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties...
QPointF circlePoint(QPointF center, qreal radius, qreal angle)
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
bool isRepeated() const
Returns whether the arrow is repeated along the line or not.
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...
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
Definition: qgssymbol.cpp:1192
bool pointsToCircle(QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius)
Compute the circumscribed circle from three points.
QgsArrowSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setHeadLength(double length)
Sets the arrow head length.
void setHeadThickness(double thickness)
Sets the arrow head height.
QgsUnitTypes::RenderUnit arrowStartWidthUnit() const
Gets the unit for the arrow start width.
void spiralArcTo(QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction)
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer&#39;s property collection, used for data defined overrides...
void setArrowWidth(double width)
Sets the arrow width.
ArrowType
Possible arrow types.
bool isCurved() const
Returns whether it is a curved arrow or a straight one.
qreal euclidian_distance(QPointF po, QPointF pd)
double headLength() const
Gets the current arrow head length.
void setIsRepeated(bool isRepeated)
Sets whether the arrow is repeated along the line.
QgsArrowSymbolLayer()
Simple constructor.
double offset() const
Returns the line&#39;s offset.
QgsMapUnitScale arrowWidthUnitScale() const
Gets the scale for the arrow width.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
Single scope for storing variables and functions for use within a QgsExpressionContext.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgssymbol.h:668
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style...
Definition: qgssymbol.h:724
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsUnitTypes::RenderUnit arrowWidthUnit() const
Gets the unit for the arrow width.
QgsStringMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setIsCurved(bool isCurved)
Sets whether it is a curved arrow or a straight one.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context...
Fill symbol.
Definition: qgssymbol.h:87
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setHeadLengthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the head length.
RenderRingFilter mRingFilter
void setArrowType(ArrowType type)
Sets the arrow type.
SymbolType type() const
Returns the symbol&#39;s type.
Definition: qgssymbol.h:120
void setArrowWidthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the arrow width.
QgsUnitTypes::RenderUnit headLengthUnit() const
Gets the unit for the head length.
void setHeadType(HeadType type)
Sets the head type.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
const QgsFeature * feature() const
Returns the current feature being rendered.
Definition: qgssymbol.h:749
void setOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the line&#39;s offset.
void setRingFilter(QgsLineSymbolLayer::RenderRingFilter filter)
Sets the line symbol layer&#39;s ring filter, which controls which rings are rendered when the line symbo...
static QgsSymbolLayer * create(const QgsStringMap &properties=QgsStringMap())
Create a new QgsArrowSymbolLayer.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
QColor color() const override
The fill color.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties...
QgsMapUnitScale headThicknessUnitScale() const
Gets the scale for the head height.
Line symbol layer used for representing lines as arrows.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
HeadType
Possible head types.
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the line&#39;s offset.
void setArrowStartWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the arrow start width.
HeadType headType() const
Gets the current head type.
QgsPropertyCollection mDataDefinedProperties
void setArrowStartWidth(double width)
Sets the arrow start width.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
void setArrowStartWidthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the arrow start width.
void setOffset(double offset)
Sets the line&#39;s offset.
void startRender(QgsSymbolRenderContext &context) override
double headThickness() const
Gets the current arrow head height.