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