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