QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 
20  , mArrowWidth( 1.0 )
21  , mArrowWidthUnit( QgsSymbolV2::MM )
22  , mArrowStartWidth( 1.0 )
23  , mArrowStartWidthUnit( QgsSymbolV2::MM )
24  , mHeadLength( 1.5 )
25  , mHeadLengthUnit( QgsSymbolV2::MM )
26  , mHeadThickness( 1.5 )
27  , mHeadThicknessUnit( QgsSymbolV2::MM )
28  , mHeadType( HeadSingle )
29  , mArrowType( ArrowPlain )
30  , mIsCurved( true )
31  , mIsRepeated( true )
32  , mScaledArrowWidth( 1.0 )
33  , mScaledArrowStartWidth( 1.0 )
34  , mScaledHeadLength( 1.5 )
35  , mScaledHeadThickness( 1.5 )
36  , mScaledOffset( 0.0 )
37  , mComputedHeadType( HeadSingle )
38  , mComputedArrowType( ArrowPlain )
39 {
40  /* default values */
41  setOffset( 0.0 );
43 
44  mSymbol.reset( static_cast<QgsFillSymbolV2*>( QgsFillSymbolV2::createSimple( QgsStringMap() ) ) );
45 }
46 
48 {
49  if ( symbol && symbol->type() == QgsSymbolV2::Fill )
50  {
51  mSymbol.reset( static_cast<QgsFillSymbolV2*>( symbol ) );
52  return true;
53  }
54  delete symbol;
55  return false;
56 }
57 
59 {
61 
62  if ( props.contains( "arrow_width" ) )
63  l->setArrowWidth( props["arrow_width"].toDouble() );
64 
65  if ( props.contains( "arrow_width_unit" ) )
66  l->setArrowWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["arrow_width_unit"] ) );
67 
68  if ( props.contains( "arrow_width_unit_scale" ) )
69  l->setArrowWidthUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["arrow_width_unit_scale"] ) );
70 
71  if ( props.contains( "arrow_start_width" ) )
72  l->setArrowStartWidth( props["arrow_start_width"].toDouble() );
73 
74  if ( props.contains( "arrow_start_width_unit" ) )
75  l->setArrowStartWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["arrow_start_width_unit"] ) );
76 
77  if ( props.contains( "arrow_start_width_unit_scale" ) )
78  l->setArrowStartWidthUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["arrow_start_width_unit_scale"] ) );
79 
80  if ( props.contains( "is_curved" ) )
81  l->setIsCurved( props["is_curved"].toInt() == 1 );
82 
83  if ( props.contains( "is_repeated" ) )
84  l->setIsRepeated( props["is_repeated"].toInt() == 1 );
85 
86  if ( props.contains( "head_length" ) )
87  l->setHeadLength( props["head_length"].toDouble() );
88 
89  if ( props.contains( "head_length_unit" ) )
90  l->setHeadLengthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["head_length_unit"] ) );
91 
92  if ( props.contains( "head_length_unit_scale" ) )
93  l->setHeadLengthUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["head_length_unit_scale"] ) );
94 
95  if ( props.contains( "head_thickness" ) )
96  l->setHeadThickness( props["head_thickness"].toDouble() );
97 
98  if ( props.contains( "head_thickness_unit" ) )
99  l->setHeadThicknessUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["head_thickness_unit"] ) );
100 
101  if ( props.contains( "head_thickness_unit_scale" ) )
102  l->setHeadThicknessUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["head_thickness_unit_scale"] ) );
103 
104  if ( props.contains( "head_type" ) )
105  l->setHeadType( static_cast<HeadType>( props["head_type"].toInt() ) );
106 
107  if ( props.contains( "arrow_type" ) )
108  l->setArrowType( static_cast<ArrowType>( props["arrow_type"].toInt() ) );
109 
110  if ( props.contains( "offset" ) )
111  l->setOffset( props["offset"].toDouble() );
112 
113  if ( props.contains( "offset_unit" ) )
114  l->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
115 
116  if ( props.contains( "offset_unit_scale" ) )
117  l->setOffsetMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["offset_unit_scale"] ) );
118 
119  l->restoreDataDefinedProperties( props );
120 
122 
123  return l;
124 }
125 
127 {
128  QgsArrowSymbolLayer* l = static_cast<QgsArrowSymbolLayer*>( create( properties() ) );
129  l->setSubSymbol( mSymbol->clone() );
131  copyPaintEffect( l );
132  return l;
133 }
134 
136 {
137  return "ArrowLine";
138 }
139 
141 {
142  QgsStringMap map;
143 
144  map["arrow_width"] = QString::number( arrowWidth() );
145  map["arrow_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( arrowWidthUnit() );
146  map["arrow_width_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( arrowWidthUnitScale() );
147 
148  map["arrow_start_width"] = QString::number( arrowStartWidth() );
149  map["arrow_start_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( arrowStartWidthUnit() );
150  map["arrow_start_width_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( arrowStartWidthUnitScale() );
151 
152  map["is_curved"] = QString::number( isCurved() ? 1 : 0 );
153  map["is_repeated"] = QString::number( isRepeated() ? 1 : 0 );
154 
155  map["head_length"] = QString::number( headLength() );
156  map["head_length_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( headLengthUnit() );
157  map["head_length_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( headLengthUnitScale() );
158 
159  map["head_thickness"] = QString::number( headThickness() );
160  map["head_thickness_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( headThicknessUnit() );
161  map["head_thickness_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( headThicknessUnitScale() );
162 
163  map["head_type"] = QString::number( headType() );
164  map["arrow_type"] = QString::number( arrowType() );
165 
166  map["offset"] = QString::number( offset() );
167  map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( offsetUnit() );
168  map["offset_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( offsetMapUnitScale() );
169 
171  return map;
172 }
173 
175 {
177 
178  attributes.unite( mSymbol->usedAttributes() );
179 
180  return attributes;
181 }
182 
183 
185 {
186  mExpressionScope.reset( new QgsExpressionContextScope() );
192  mComputedHeadType = headType();
193  mComputedArrowType = arrowType();
194 
195  mSymbol->startRender( context.renderContext() );
196 }
197 
199 {
200  mSymbol->stopRender( context.renderContext() );
201 }
202 
203 inline qreal euclidian_distance( const QPointF& po, const QPointF& pd )
204 {
205  return sqrt(( po.x() - pd.x() ) * ( po.x() - pd.x() ) + ( po.y() - pd.y() ) * ( po.y() - pd.y() ) );
206 }
207 
209  qreal startWidth, qreal width,
210  qreal headWidth, qreal headHeight,
212  qreal offset )
213 {
214  QPolygonF polygon; // implicitly shared
215  // vector length
216  qreal length = euclidian_distance( po, pd );
217 
218  // shift points if there is not enough room for the head(s)
219  if (( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
220  {
221  po = pd - ( pd - po ) / length * headWidth;
222  length = headWidth;
223  }
224  else if (( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
225  {
226  pd = po + ( pd - po ) / length * headWidth;
227  length = headWidth;
228  }
229  else if (( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
230  {
231  QPointF v = ( pd - po ) / length * headWidth;
232  QPointF npo = ( po + pd ) / 2.0 - v;
233  QPointF npd = ( po + pd ) / 2.0 + v;
234  po = npo;
235  pd = npd;
236  length = 2 * headWidth;
237  }
238 
239  qreal bodyLength = length - headWidth;
240 
241  // unit vector
242  QPointF unitVec = ( pd - po ) / length;
243  // perpendicular vector
244  QPointF perpVec( -unitVec.y(), unitVec.x() );
245 
246  // set offset
247  po += perpVec * offset;
248  pd += perpVec * offset;
249 
250  if ( headType == QgsArrowSymbolLayer::HeadDouble )
251  {
252  // first head
253  polygon << po;
254  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
255  {
256  polygon << po + unitVec * headWidth + perpVec * headHeight;
257  polygon << po + unitVec * headWidth + perpVec * ( width * 0.5 );
258 
259  polygon << po + unitVec * bodyLength + perpVec * ( width * 0.5 );
260 
261  // second head
262  polygon << po + unitVec * bodyLength + perpVec * headHeight;
263  }
264  polygon << pd;
265 
266  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
267  {
268  polygon << po + unitVec * bodyLength - perpVec * headHeight;
269  polygon << po + unitVec * bodyLength - perpVec * ( width * 0.5 );
270 
271  // end of the first head
272  polygon << po + unitVec * headWidth - perpVec * ( width * 0.5 );
273  polygon << po + unitVec * headWidth - perpVec * headHeight;
274  }
275  }
276  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
277  {
278  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
279  {
280  polygon << po + perpVec * ( startWidth * 0.5 );
281  polygon << po + unitVec * bodyLength + perpVec * ( width * 0.5 );
282  polygon << po + unitVec * bodyLength + perpVec * headHeight;
283  }
284  else
285  {
286  polygon << po;
287  }
288  polygon << pd;
289  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
290  {
291  polygon << po + unitVec * bodyLength - perpVec * headHeight;
292  polygon << po + unitVec * bodyLength - perpVec * ( width * 0.5 );
293  polygon << po - perpVec * ( startWidth * 0.5 );
294  }
295  else
296  {
297  polygon << po;
298  }
299  }
300  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
301  {
302  polygon << po;
303  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
304  {
305  polygon << po + unitVec * headWidth + perpVec * headHeight;
306  polygon << po + unitVec * headWidth + perpVec * ( width * 0.5 );
307 
308  polygon << pd + perpVec * ( startWidth * 0.5 );
309  }
310  else
311  {
312  polygon << pd;
313  }
314  if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
315  {
316  polygon << pd - perpVec * ( startWidth * 0.5 );
317 
318  polygon << po + unitVec * headWidth - perpVec * ( width * 0.5 );
319  polygon << po + unitVec * headWidth - perpVec * headHeight;
320  }
321  else
322  {
323  polygon << pd;
324  }
325  }
326  // close the polygon
327  polygon << polygon.first();
328 
329  return polygon;
330 }
331 
332 // Make sure a given angle is between 0 and 2 pi
333 inline qreal clampAngle( qreal a )
334 {
335  if ( a > 2 * M_PI )
336  return a - 2 * M_PI;
337  if ( a < 0.0 )
338  return a + 2 * M_PI;
339  return a;
340 }
341 
346 bool pointsToCircle( const QPointF& a, const QPointF& b, const QPointF& c, QPointF& center, qreal& radius )
347 {
348  qreal cx, cy;
349 
350  // AB and BC vectors
351  QPointF ab = b - a;
352  QPointF bc = c - b;
353 
354  // AB and BC middles
355  QPointF ab2 = ( a + b ) / 2.0;
356  QPointF bc2 = ( b + c ) / 2.0;
357 
358  // Aligned points
359  if ( fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
360  return false;
361 
362  // in case AB is horizontal
363  if ( ab.y() == 0 )
364  {
365  cx = ab2.x();
366  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
367  }
368  //# BC horizontal
369  else if ( bc.y() == 0 )
370  {
371  cx = bc2.x();
372  cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
373  }
374  // Otherwise
375  else
376  {
377  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() );
378  cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
379  }
380  // Radius
381  radius = sqrt(( a.x() - cx ) * ( a.x() - cx ) + ( a.y() - cy ) * ( a.y() - cy ) );
382  // Center
383  center.setX( cx );
384  center.setY( cy );
385  return true;
386 }
387 
388 QPointF circlePoint( const QPointF& center, qreal radius, qreal angle )
389 {
390  // Y is oriented downward
391  return QPointF( cos( -angle ) * radius + center.x(), sin( -angle ) * radius + center.y() );
392 }
393 
394 void pathArcTo( QPainterPath& path, const QPointF& circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
395 {
396  QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
397  if ( direction == 1 )
398  {
399  if ( angle_o < angle_d )
400  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
401  else
402  path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
403  }
404  else
405  {
406  if ( angle_o < angle_d )
407  path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
408  else
409  path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
410  }
411 }
412 
413 // Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
414 void spiralArcTo( QPainterPath& path, const QPointF& center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
415 {
416  // start point
417  QPointF A = circlePoint( center, startRadius, startAngle );
418  // end point
419  QPointF B = circlePoint( center, endRadius, endAngle );
420  // middle points
421  qreal deltaAngle;
422 
423  deltaAngle = endAngle - startAngle;
424  if ( direction * deltaAngle < 0.0 )
425  deltaAngle = deltaAngle + direction * 2 * M_PI;
426 
427  QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
428  QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
429  QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
430 
431  qreal cRadius;
432  QPointF cCenter;
433  // first circle arc
434  if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
435  {
436  // aligned points => draw a straight line
437  path.lineTo( I2 );
438  }
439  else
440  {
441  // angles in the new circle
442  qreal a1 = atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
443  qreal a2 = atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
444  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
445  }
446 
447  // second circle arc
448  if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
449  {
450  // aligned points => draw a straight line
451  path.lineTo( B );
452  }
453  else
454  {
455  // angles in the new circle
456  qreal a1 = atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
457  qreal a2 = atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
458  pathArcTo( path, cCenter, cRadius, a1, a2, direction );
459  }
460 }
461 
463  qreal startWidth, qreal width,
464  qreal headWidth, qreal headHeight,
466  qreal offset )
467 {
468  qreal circleRadius;
469  QPointF circleCenter;
470  if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
471  {
472  // aligned points => draw a straight arrow
473  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
474  }
475 
476  // angles of each point
477  qreal angle_o = clampAngle( atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
478  qreal angle_m = clampAngle( atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
479  qreal angle_d = clampAngle( atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
480 
481  // arc direction : 1 = counter-clockwise, -1 = clockwise
482  int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
483 
484  // arrow type, independent of the direction
485  int aType = 0;
486  if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
487  aType = direction;
488  else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
489  aType = -direction;
490 
491  qreal deltaAngle = angle_d - angle_o;
492  if ( direction * deltaAngle < 0.0 )
493  deltaAngle = deltaAngle + direction * 2 * M_PI;
494 
495  qreal length = euclidian_distance( po, pd );
496  // for close points and deltaAngle < 180, draw a straight line
497  if ( fabs( deltaAngle ) < M_PI && ((( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
498  (( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
499  (( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2*headWidth ) ) ) )
500  {
501  return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
502  }
503 
504  // ajust coordinates to include offset
505  circleRadius += offset;
506  po = circlePoint( circleCenter, circleRadius, angle_o );
507  pm = circlePoint( circleCenter, circleRadius, angle_m );
508  pd = circlePoint( circleCenter, circleRadius, angle_d );
509 
510  qreal headAngle = direction * atan( headWidth / circleRadius );
511 
512  QPainterPath path;
513 
514  if ( headType == QgsArrowSymbolLayer::HeadDouble )
515  {
516  // the first head
517  path.moveTo( po );
518  if ( aType <= 0 )
519  {
520  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
521 
522  pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
523 
524  // the second head
525  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
526  path.lineTo( pd );
527  }
528  else
529  {
530  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
531  }
532  if ( aType >= 0 )
533  {
534  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
535 
536  pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
537 
538  // the end of the first head
539  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
540  path.lineTo( po );
541  }
542  else
543  {
544  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
545  }
546  }
547  else if ( headType == QgsArrowSymbolLayer::HeadSingle )
548  {
549  if ( aType <= 0 )
550  {
551  path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
552 
553  spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
554 
555  // the arrow head
556  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
557  path.lineTo( pd );
558  }
559  else
560  {
561  path.moveTo( po );
562  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
563  }
564  if ( aType >= 0 )
565  {
566  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
567 
568  spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
569 
570  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
571  }
572  else
573  {
574  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
575  path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
576  }
577  }
578  else if ( headType == QgsArrowSymbolLayer::HeadReversed )
579  {
580  path.moveTo( po );
581  if ( aType <= 0 )
582  {
583  path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
584  path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
585 
586  spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
587  }
588  else
589  {
590  pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
591  }
592  if ( aType >= 0 )
593  {
594  path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
595 
596  spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
597 
598  path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
599  path.lineTo( po );
600  }
601  else
602  {
603  path.lineTo( pd );
604  pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
605  }
606  }
607 
608  return path.toSubpathPolygons().at( 0 );
609 }
610 
611 void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolV2RenderContext& context )
612 {
613  if ( !hasDataDefinedProperties() )
614  return; // shortcut if case there is no data defined properties at all
615 
616  bool ok;
617  if ( hasDataDefinedProperty( "arrow_width" ) )
618  {
620  double w = evaluateDataDefinedProperty( "arrow_width", context, QVariant(), &ok ).toDouble();
621  if ( ok )
622  {
624  }
625  }
626  if ( hasDataDefinedProperty( "arrow_start_width" ) )
627  {
629  double w = evaluateDataDefinedProperty( "arrow_start_width", context, QVariant(), &ok ).toDouble();
630  if ( ok )
631  {
633  }
634  }
635  if ( hasDataDefinedProperty( "head_length" ) )
636  {
638  double w = evaluateDataDefinedProperty( "head_length", context, QVariant(), &ok ).toDouble();
639  if ( ok )
640  {
642  }
643  }
644  if ( hasDataDefinedProperty( "head_thickness" ) )
645  {
647  double w = evaluateDataDefinedProperty( "head_thickness", context, QVariant(), &ok ).toDouble();
648  if ( ok )
649  {
651  }
652  }
654  {
655  context.setOriginalValueVariable( offset() );
657  if ( ok )
658  {
660  }
661  }
662 
663  if ( hasDataDefinedProperty( "head_type" ) )
664  {
665  context.setOriginalValueVariable( headType() );
666  int h = evaluateDataDefinedProperty( "head_type", context, QVariant(), &ok ).toInt();
667  if ( ok )
668  {
669  mComputedHeadType = static_cast<HeadType>( h );
670  }
671  }
672 
673  if ( hasDataDefinedProperty( "arrow_type" ) )
674  {
675  context.setOriginalValueVariable( arrowType() );
676  int h = evaluateDataDefinedProperty( "arrow_type", context, QVariant(), &ok ).toInt();
677  if ( ok )
678  {
679  mComputedArrowType = static_cast<ArrowType>( h );
680  }
681  }
682 }
683 
685 {
686  Q_UNUSED( points );
687 
688  if ( !context.renderContext().painter() )
689  {
690  return;
691  }
692 
693  context.renderContext().expressionContext().appendScope( mExpressionScope.data() );
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  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
731  _resolveDataDefined( context );
732 
733  if ( points.size() - pIdx >= 3 )
734  {
735  // origin point
736  QPointF po( points.at( pIdx ) );
737  // middle point
738  QPointF pm( points.at( pIdx + 1 ) );
739  // destination point
740  QPointF pd( points.at( pIdx + 2 ) );
741 
742  QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
743  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
744  }
745  // straight arrow
746  else if ( points.size() - pIdx == 2 )
747  {
748  // origin point
749  QPointF po( points.at( pIdx ) );
750  // destination point
751  QPointF pd( points.at( pIdx + 1 ) );
752 
753  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
754  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
755  }
756  }
757  }
758  }
759  else
760  {
761  if ( !isRepeated() )
762  {
763  _resolveDataDefined( context );
764 
765  if ( !points.isEmpty() )
766  {
767  // origin point
768  QPointF po( points.at( 0 ) );
769  // destination point
770  QPointF pd( points.back() );
771 
772  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
773  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
774  }
775  }
776  else
777  {
778  // only straight arrows
779  for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
780  {
781  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
782  _resolveDataDefined( context );
783 
784  // origin point
785  QPointF po( points.at( pIdx ) );
786  // destination point
787  QPointF pd( points.at( pIdx + 1 ) );
788 
789  QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
790  mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
791  }
792  }
793  }
795 }
796 
798 {
799  if ( mSymbol.data() )
800  mSymbol->setColor( c );
801 
802  mColor = c;
803 }
804 
806 {
807  return mSymbol.data() ? mSymbol->color() : mColor;
808 }
809 
static QString encodeOutputUnit(QgsSymbolV2::OutputUnit unit)
void setHeadThicknessUnitScale(const QgsMapUnitScale &scale)
Set the scale for the head height.
Single variable definition for use within a QgsExpressionContextScope.
void startRender(QgsSymbolV2RenderContext &context) override
Prepare the rendering.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
bool contains(const Key &key) const
double arrowStartWidth() const
Get current arrow start width.
double arrowWidth() const
Get current arrow width.
QgsSymbolV2::OutputUnit headThicknessUnit() const
Get the unit for the head height.
virtual QSet< QString > usedAttributes() const
Returns the set of attributes referenced by the layer.
qreal euclidian_distance(const QPointF &po, const QPointF &pd)
static QgsFillSymbolV2 * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
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.
virtual bool hasDataDefinedProperties() const
Checks whether the layer has any associated data defined properties.
QgsMapUnitScale headLengthUnitScale() const
Get the scale for the head length.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
QString layerType() const override
Returns a string that represents this layer type.
void moveTo(const QPointF &point)
SymbolType type() const
Definition: qgssymbolv2.h:107
void setColor(const QColor &c) override
The fill color.
void pathArcTo(QPainterPath &path, const QPointF &circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction)
const QgsFeature * feature() const
Current feature being rendered - may be null.
Definition: qgssymbolv2.h:388
void setArrowWidthUnit(QgsSymbolV2::OutputUnit unit)
Set the unit for the arrow width.
const QgsMapUnitScale & offsetMapUnitScale() const
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
T & first()
qreal clampAngle(qreal a)
void renderPolyline(const QPolygonF &points, QgsSymbolV2RenderContext &context) override
Main drawing method.
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.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:492
static const QString EXPR_OFFSET
reference back()
virtual Q_DECL_DEPRECATED QVariant evaluateDataDefinedProperty(const QString &property, const QgsFeature *feature, const QVariant &defaultVal=QVariant(), bool *ok=nullptr) const
Evaluates the matching data defined property and returns the calculated value.
void stopRender(QgsSymbolV2RenderContext &context) override
End of the rendering.
void reset(T *other)
static QgsSymbolV2::OutputUnit decodeOutputUnit(const QString &str)
bool isRepeated() const
Return whether the arrow is repeated along the line or not.
virtual double width() const
The output shall be in millimeters.
Definition: qgssymbolv2.h:67
QString number(int n, int base)
qreal x() const
qreal y() const
void setOffset(double offset)
int toInt(bool *ok) const
virtual QgsArrowSymbolLayer * clone() const override
Virtual constructor.
static double convertToPainterUnits(const QgsRenderContext &c, double size, QgsSymbolV2::OutputUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale())
Converts a size from the specied units to painter units.
void setHeadLength(double length)
Set the arrow head length.
void setHeadThickness(double thickness)
Set the arrow head height.
static QgsSymbolLayerV2 * create(const QgsStringMap &properties=QgsStringMap())
Create a new QgsArrowSymbolLayer.
void lineTo(const QPointF &endPoint)
QList< QPolygonF > toSubpathPolygons(const QMatrix &matrix) const
void spiralArcTo(QPainterPath &path, const QPointF &center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction)
#define M_PI
void setHeadThicknessUnit(QgsSymbolV2::OutputUnit unit)
Set the unit for the head height.
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.
virtual QSet< QString > usedAttributes() const override
Return a list of attributes required to render this feature.
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.
QgsMapUnitScale arrowWidthUnitScale() const
Get the scale for the arrow width.
Single scope for storing variables and functions for use within a QgsExpressionContext.
T * data() const
void copyDataDefinedProperties(QgsSymbolLayerV2 *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
double ANALYSIS_EXPORT angle(Point3D *p1, Point3D *p2, Point3D *p3, Point3D *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
void saveDataDefinedProperties(QgsStringMap &stringMap) const
Saves all data defined properties to a string map.
void setOffsetUnit(QgsSymbolV2::OutputUnit unit)
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsStringMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void copyPaintEffect(QgsSymbolLayerV2 *destLayer) const
Copies paint effect of this layer to another symbol layer.
void setIsCurved(bool isCurved)
Set whether it is a curved arrow or a straight one.
const T & at(int i) const
QPainter * painter()
void setHeadLengthUnitScale(const QgsMapUnitScale &scale)
Set the scale for the head length.
QSet< T > & unite(const QSet< T > &other)
void setArrowType(ArrowType type)
Set the arrow type.
QgsSymbolV2::OutputUnit offsetUnit() const
virtual bool hasDataDefinedProperty(const QString &property) const
Checks whether the layer has a matching data defined property and if that property is currently activ...
QgsSymbolV2::OutputUnit headLengthUnit() const
Get the unit for the head length.
void setArrowWidthUnitScale(const QgsMapUnitScale &scale)
Set the scale for the arrow width.
bool isEmpty() const
void setArrowStartWidthUnit(QgsSymbolV2::OutputUnit unit)
Set the unit for the arrow start width.
void setHeadType(HeadType type)
Set the head type.
QgsRenderContext & renderContext()
Definition: qgssymbolv2.h:359
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setX(qreal x)
void setY(qreal y)
bool pointsToCircle(const QPointF &a, const QPointF &b, const QPointF &c, QPointF &center, qreal &radius)
Compute the circumscribed circle from three points.
Fill symbol.
Definition: qgssymbolv2.h:83
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
virtual QColor color() const override
The fill color.
double toDouble(bool *ok) const
QgsSymbolV2::OutputUnit arrowStartWidthUnit() const
Get the unit for the arrow start width.
void setHeadLengthUnit(QgsSymbolV2::OutputUnit unit)
Set the unit for the head length.
QgsMapUnitScale headThicknessUnitScale() const
Get the scale for the head height.
Line symbol layer used for representing lines as arrows.
void restoreDataDefinedProperties(const QgsStringMap &stringMap)
Restores all data defined properties from string map.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
virtual bool setSubSymbol(QgsSymbolV2 *symbol) override
Set the sub symbol used for filling.
HeadType
Possible head types.
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
QgsSymbolV2::OutputUnit arrowWidthUnit() const
Get the unit for the arrow width.
int size() const
void arcTo(const QRectF &rectangle, qreal startAngle, qreal sweepLength)
HeadType headType() const
Get the current head type.
void setArrowStartWidth(double width)
Set the arrow start width.
QPointF circlePoint(const QPointF &center, qreal radius, qreal angle)
void setArrowStartWidthUnitScale(const QgsMapUnitScale &scale)
Set the scale for the arrow start width.
double headThickness() const
Get the current arrow head height.