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