QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
101 
103 
104  return l;
105 }
106 
108 {
109  QgsArrowSymbolLayer *l = static_cast<QgsArrowSymbolLayer *>( create( properties() ) );
110  l->setSubSymbol( mSymbol->clone() );
112  copyPaintEffect( l );
113  return l;
114 }
115 
117 {
118  return QStringLiteral( "ArrowLine" );
119 }
120 
122 {
123  QgsStringMap map;
124 
125  map[QStringLiteral( "arrow_width" )] = QString::number( arrowWidth() );
126  map[QStringLiteral( "arrow_width_unit" )] = QgsUnitTypes::encodeUnit( arrowWidthUnit() );
127  map[QStringLiteral( "arrow_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowWidthUnitScale() );
128 
129  map[QStringLiteral( "arrow_start_width" )] = QString::number( arrowStartWidth() );
130  map[QStringLiteral( "arrow_start_width_unit" )] = QgsUnitTypes::encodeUnit( arrowStartWidthUnit() );
131  map[QStringLiteral( "arrow_start_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowStartWidthUnitScale() );
132 
133  map[QStringLiteral( "is_curved" )] = QString::number( isCurved() ? 1 : 0 );
134  map[QStringLiteral( "is_repeated" )] = QString::number( isRepeated() ? 1 : 0 );
135 
136  map[QStringLiteral( "head_length" )] = QString::number( headLength() );
137  map[QStringLiteral( "head_length_unit" )] = QgsUnitTypes::encodeUnit( headLengthUnit() );
138  map[QStringLiteral( "head_length_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headLengthUnitScale() );
139 
140  map[QStringLiteral( "head_thickness" )] = QString::number( headThickness() );
141  map[QStringLiteral( "head_thickness_unit" )] = QgsUnitTypes::encodeUnit( headThicknessUnit() );
142  map[QStringLiteral( "head_thickness_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headThicknessUnitScale() );
143 
144  map[QStringLiteral( "head_type" )] = QString::number( headType() );
145  map[QStringLiteral( "arrow_type" )] = QString::number( arrowType() );
146 
147  map[QStringLiteral( "offset" )] = QString::number( offset() );
148  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( offsetUnit() );
149  map[QStringLiteral( "offset_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetMapUnitScale() );
150 
151  return map;
152 }
153 
154 QSet<QString> QgsArrowSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
155 {
156  QSet<QString> attributes = QgsLineSymbolLayer::usedAttributes( context );
157 
158  attributes.unite( mSymbol->usedAttributes( context ) );
159 
160  return attributes;
161 }
162 
163 
165 {
166  mExpressionScope.reset( new QgsExpressionContextScope() );
167  mScaledArrowWidth = context.renderContext().convertToPainterUnits( arrowWidth(), arrowWidthUnit(), arrowWidthUnitScale() );
169  mScaledHeadLength = context.renderContext().convertToPainterUnits( headLength(), headLengthUnit(), headLengthUnitScale() );
171  mScaledOffset = context.renderContext().convertToPainterUnits( offset(), offsetUnit(), offsetMapUnitScale() );
172  mComputedHeadType = headType();
173  mComputedArrowType = arrowType();
174 
175  mSymbol->startRender( context.renderContext() );
176 }
177 
179 {
180  mSymbol->stopRender( context.renderContext() );
181 }
182 
183 inline qreal euclidian_distance( QPointF po, QPointF pd )
184 {
185  return std::sqrt( ( po.x() - pd.x() ) * ( po.x() - pd.x() ) + ( po.y() - pd.y() ) * ( po.y() - pd.y() ) );
186 }
187 
188 QPolygonF straightArrow( QPointF po, QPointF pd,
189  qreal startWidth, qreal width,
190  qreal headWidth, qreal headHeight,
192  qreal offset )
193 {
194  QPolygonF polygon; // implicitly shared
195  // vector length
196  qreal length = euclidian_distance( po, pd );
197 
198  // shift points if there is not enough room for the head(s)
199  if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
200  {
201  po = pd - ( pd - po ) / length * headWidth;
202  length = headWidth;
203  }
204  else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
205  {
206  pd = po + ( pd - po ) / length * headWidth;
207  length = headWidth;
208  }
209  else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
210  {
211  QPointF v = ( pd - po ) / length * headWidth;
212  QPointF npo = ( po + pd ) / 2.0 - v;
213  QPointF npd = ( po + pd ) / 2.0 + v;
214  po = npo;
215  pd = npd;
216  length = 2 * headWidth;
217  }
218 
219  qreal bodyLength = length - headWidth;
220 
221  // unit vector
222  QPointF unitVec = ( pd - po ) / length;
223  // perpendicular vector
224  QPointF perpVec( -unitVec.y(), unitVec.x() );
225 
226  // set offset
227  po += perpVec * offset;
228  pd += perpVec * offset;
229 
230  if ( headType == QgsArrowSymbolLayer::HeadDouble )
231  {
232  // first head
233  polygon << po;
234  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
235  {
236  polygon << po + unitVec *headWidth + perpVec *headHeight;
237  polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
238 
239  polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
240 
241  // second head
242  polygon << po + unitVec *bodyLength + perpVec *headHeight;
243  }
244  polygon << pd;
245 
246  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
247  {
248  polygon << po + unitVec *bodyLength - perpVec *headHeight;
249  polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
250 
251  // end of the first head
252  polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
253  polygon << po + unitVec *headWidth - perpVec *headHeight;
254  }
255  }
256  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
257  {
258  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
259  {
260  polygon << po + perpVec * ( startWidth * 0.5 );
261  polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
262  polygon << po + unitVec *bodyLength + perpVec *headHeight;
263  }
264  else
265  {
266  polygon << po;
267  }
268  polygon << pd;
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  polygon << po - perpVec * ( startWidth * 0.5 );
274  }
275  else
276  {
277  polygon << po;
278  }
279  }
280  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
281  {
282  polygon << po;
283  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
284  {
285  polygon << po + unitVec *headWidth + perpVec *headHeight;
286  polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
287 
288  polygon << pd + perpVec * ( startWidth * 0.5 );
289  }
290  else
291  {
292  polygon << pd;
293  }
294  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
295  {
296  polygon << pd - perpVec * ( startWidth * 0.5 );
297 
298  polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
299  polygon << po + unitVec *headWidth - perpVec *headHeight;
300  }
301  else
302  {
303  polygon << pd;
304  }
305  }
306  // close the polygon
307  polygon << polygon.first();
308 
309  return polygon;
310 }
311 
312 // Make sure a given angle is between 0 and 2 pi
313 inline qreal clampAngle( qreal a )
314 {
315  if ( a > 2 * M_PI )
316  return a - 2 * M_PI;
317  if ( a < 0.0 )
318  return a + 2 * M_PI;
319  return a;
320 }
321 
326 bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius )
327 {
328  qreal cx, cy;
329 
330  // AB and BC vectors
331  QPointF ab = b - a;
332  QPointF bc = c - b;
333 
334  // AB and BC middles
335  QPointF ab2 = ( a + b ) / 2.0;
336  QPointF bc2 = ( b + c ) / 2.0;
337 
338  // Aligned points
339  if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
340  return false;
341 
342  // in case AB is horizontal
343  if ( ab.y() == 0 )
344  {
345  cx = ab2.x();
346  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
347  }
348  //# BC horizontal
349  else if ( bc.y() == 0 )
350  {
351  cx = bc2.x();
352  cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
353  }
354  // Otherwise
355  else
356  {
357  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() );
358  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
359  }
360  // Radius
361  radius = std::sqrt( ( a.x() - cx ) * ( a.x() - cx ) + ( a.y() - cy ) * ( a.y() - cy ) );
362  // Center
363  center.setX( cx );
364  center.setY( cy );
365  return true;
366 }
367 
368 QPointF circlePoint( QPointF center, qreal radius, qreal angle )
369 {
370  // Y is oriented downward
371  return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
372 }
373 
374 void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
375 {
376  QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
377  if ( direction == 1 )
378  {
379  if ( angle_o < angle_d )
380  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
381  else
382  path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
383  }
384  else
385  {
386  if ( angle_o < angle_d )
387  path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
388  else
389  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
390  }
391 }
392 
393 // Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
394 void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
395 {
396  // start point
397  QPointF A = circlePoint( center, startRadius, startAngle );
398  // end point
399  QPointF B = circlePoint( center, endRadius, endAngle );
400  // middle points
401  qreal deltaAngle;
402 
403  deltaAngle = endAngle - startAngle;
404  if ( direction * deltaAngle < 0.0 )
405  deltaAngle = deltaAngle + direction * 2 * M_PI;
406 
407  QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
408  QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
409  QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
410 
411  qreal cRadius;
412  QPointF cCenter;
413  // first circle arc
414  if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
415  {
416  // aligned points => draw a straight line
417  path.lineTo( I2 );
418  }
419  else
420  {
421  // angles in the new circle
422  qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
423  qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
424  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
425  }
426 
427  // second circle arc
428  if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
429  {
430  // aligned points => draw a straight line
431  path.lineTo( B );
432  }
433  else
434  {
435  // angles in the new circle
436  qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
437  qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
438  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
439  }
440 }
441 
442 QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
443  qreal startWidth, qreal width,
444  qreal headWidth, qreal headHeight,
446  qreal offset )
447 {
448  qreal circleRadius;
449  QPointF circleCenter;
450  if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
451  {
452  // aligned points => draw a straight arrow
453  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
454  }
455 
456  // angles of each point
457  qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
458  qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
459  qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
460 
461  // arc direction : 1 = counter-clockwise, -1 = clockwise
462  int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
463 
464  // arrow type, independent of the direction
465  int aType = 0;
466  if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
467  aType = direction;
468  else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
469  aType = -direction;
470 
471  qreal deltaAngle = angle_d - angle_o;
472  if ( direction * deltaAngle < 0.0 )
473  deltaAngle = deltaAngle + direction * 2 * M_PI;
474 
475  qreal length = euclidian_distance( po, pd );
476  // for close points and deltaAngle < 180, draw a straight line
477  if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
478  ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
479  ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
480  {
481  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
482  }
483 
484  // ajust coordinates to include offset
485  circleRadius += offset;
486  po = circlePoint( circleCenter, circleRadius, angle_o );
487  pm = circlePoint( circleCenter, circleRadius, angle_m );
488  pd = circlePoint( circleCenter, circleRadius, angle_d );
489 
490  qreal headAngle = direction * std::atan( headWidth / circleRadius );
491 
492  QPainterPath path;
493 
494  if ( headType == QgsArrowSymbolLayer::HeadDouble )
495  {
496  // the first head
497  path.moveTo( po );
498  if ( aType <= 0 )
499  {
500  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
501 
502  pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
503 
504  // the second head
505  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
506  path.lineTo( pd );
507  }
508  else
509  {
510  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
511  }
512  if ( aType >= 0 )
513  {
514  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
515 
516  pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
517 
518  // the end of the first head
519  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
520  path.lineTo( po );
521  }
522  else
523  {
524  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
525  }
526  }
527  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
528  {
529  if ( aType <= 0 )
530  {
531  path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
532 
533  spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
534 
535  // the arrow head
536  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
537  path.lineTo( pd );
538  }
539  else
540  {
541  path.moveTo( po );
542  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
543  }
544  if ( aType >= 0 )
545  {
546  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
547 
548  spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
549 
550  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
551  }
552  else
553  {
554  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
555  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
556  }
557  }
558  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
559  {
560  path.moveTo( po );
561  if ( aType <= 0 )
562  {
563  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
564  path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
565 
566  spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
567  }
568  else
569  {
570  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
571  }
572  if ( aType >= 0 )
573  {
574  path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
575 
576  spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
577 
578  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
579  path.lineTo( po );
580  }
581  else
582  {
583  path.lineTo( pd );
584  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
585  }
586  }
587 
588  return path.toSubpathPolygons().at( 0 );
589 }
590 
591 void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
592 {
593  if ( !dataDefinedProperties().hasActiveProperties() )
594  return; // shortcut if case there is no data defined properties at all
595 
596  QVariant exprVal;
597  bool ok;
599  {
601  double w = exprVal.toDouble( &ok );
602  if ( ok )
603  {
604  mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
605  }
606  }
608  {
611  double w = exprVal.toDouble( &ok );
612  if ( ok )
613  {
614  mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
615  }
616  }
618  {
621  double w = exprVal.toDouble( &ok );
622  if ( ok )
623  {
624  mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
625  }
626  }
628  {
631  double w = exprVal.toDouble( &ok );
632  if ( ok )
633  {
634  mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
635  }
636  }
638  {
639  context.setOriginalValueVariable( offset() );
641  double w = exprVal.toDouble( &ok );
642  if ( ok )
643  {
644  mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
645  }
646  }
647 
649  {
650  context.setOriginalValueVariable( headType() );
653  if ( ok )
654  {
655  mComputedHeadType = h;
656  }
657  }
658 
660  {
661  context.setOriginalValueVariable( arrowType() );
664  if ( ok )
665  {
666  mComputedArrowType = h;
667  }
668  }
669 }
670 
671 void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
672 {
673  Q_UNUSED( points );
674 
675  if ( !context.renderContext().painter() )
676  {
677  return;
678  }
679 
680  context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
681  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
683  if ( isCurved() )
684  {
685  _resolveDataDefined( context );
686 
687  if ( ! isRepeated() )
688  {
689  if ( points.size() >= 3 )
690  {
691  // origin point
692  QPointF po( points.at( 0 ) );
693  // middle point
694  QPointF pm( points.at( points.size() / 2 ) );
695  // destination point
696  QPointF pd( points.back() );
697 
698  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
699  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
700  }
701  // straight arrow
702  else if ( points.size() == 2 )
703  {
704  // origin point
705  QPointF po( points.at( 0 ) );
706  // destination point
707  QPointF pd( points.at( 1 ) );
708 
709  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
710  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
711  }
712  }
713  else
714  {
715  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
716  {
717  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
718  _resolveDataDefined( context );
719 
720  if ( points.size() - pIdx >= 3 )
721  {
722  // origin point
723  QPointF po( points.at( pIdx ) );
724  // middle point
725  QPointF pm( points.at( pIdx + 1 ) );
726  // destination point
727  QPointF pd( points.at( pIdx + 2 ) );
728 
729  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
730  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
731  }
732  // straight arrow
733  else if ( points.size() - pIdx == 2 )
734  {
735  // origin point
736  QPointF po( points.at( pIdx ) );
737  // destination point
738  QPointF pd( points.at( pIdx + 1 ) );
739 
740  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
741  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
742  }
743  }
744  }
745  }
746  else
747  {
748  if ( !isRepeated() )
749  {
750  _resolveDataDefined( context );
751 
752  if ( !points.isEmpty() )
753  {
754  // origin point
755  QPointF po( points.at( 0 ) );
756  // destination point
757  QPointF pd( points.back() );
758 
759  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
760  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
761  }
762  }
763  else
764  {
765  // only straight arrows
766  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
767  {
768  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
769  _resolveDataDefined( context );
770 
771  // origin point
772  QPointF po( points.at( pIdx ) );
773  // destination point
774  QPointF pd( points.at( pIdx + 1 ) );
775 
776  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
777  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
778  }
779  }
780  }
782 }
783 
784 void QgsArrowSymbolLayer::setColor( const QColor &c )
785 {
786  if ( mSymbol )
787  mSymbol->setColor( c );
788 
789  mColor = c;
790 }
791 
793 {
794  return mSymbol.get() ? mSymbol->color() : mColor;
795 }
796 
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.
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
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:1109
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.
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:501
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
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
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:1050
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
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:453
bool selected() const
Definition: qgssymbol.h:492
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
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.
void setArrowType(ArrowType type)
Sets the arrow type.
SymbolType type() const
Definition: qgssymbol.h:113
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
Current feature being rendered - may be null.
Definition: qgssymbol.h:509
void setOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the line&#39;s offset.
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)
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)
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)
void startRender(QgsSymbolRenderContext &context) override
double headThickness() const
Gets the current arrow head height.